<filename>Lib/site-packages/sciplot/sciplotUI.py
# -*- coding: utf-8 -*-
"""
SciPlot-PyQt: Publication-ready scientific plotting for Python
==============================================================

SciPlot-PyQt (aka SciPlot) is a user-interface/matplotlib wrapper built with 
PyQt5 that allows interactive plotting through an embedded matplotlib canvas. 
It enables fast and easy publication-ready plots and images:
    
    * Interactive plotting
    * Theme and style editing (TODO)
    * Figure saving and opening for later editing (TODO)
    
Supported Plot Types
---------------------
Line plots : plot

Bar plots : bar, hist

Polycollections : fill_between

Images : imshow

Notes
-----
SciPlot has a lot of advances/improvements to make. Feel free to contact me--
help is always welcome!

Usage
-----
import sciplot
sp = sciplot.SciPlotUI()
sp.show()

Example
-------
sp.plot((0,1),(2,3),label='Line', x_label='X', y_label='Y', ls='--')
sp.fill_between((0,1),(1,2),(3,4),label='Fill Between', color='r', alpha=0.25)

Authors
-------
* <NAME>. <<EMAIL>>
"""

import sys as _sys
import os as _os
import numpy as _np
import time as _time

# Generic imports for MPL-incorporation
import matplotlib as _mpl
_mpl.use('Qt5Agg')
import matplotlib.pyplot as _plt  # Simply for it starting a QApplication

# Generic imports for QT-based programs
from PyQt5.QtWidgets import (QApplication as _QApplication,
                             QMainWindow as _QMainWindow,
                             QTableView as _QTableView,
                             QSizePolicy as _QSizePolicy,
                             QTabWidget as _QTabWidget,
                             QFileDialog as _QFileDialog,
                             QInputDialog as _QInputDialog)

from PyQt5.QtCore import pyqtSignal as _pyqtSignal
import PyQt5.QtCore as _QtCore

import sciplot

# Import from Designer-based GUI
from sciplot.ui.qt_Plotter import Ui_MainWindow as Ui_Plotter
from sciplot.ui.widget_mpl import MplCanvas as _MplCanvas
from sciplot.ui.dialogs import DualEntry

from sciplot.ui.models.lines import (TableModelLines as _TableModelLines,
                                     EditDelegateLines as _EditDelegateLines)

from sciplot.ui.models.fillbetween import (TableModelFillBetween as
                                               _TableModelFillBetween,
                                           EditDelegateFillBetween as
                                           _EditDelegateFillBetween)

from sciplot.ui.models.images import (TableModelImages as _TableModelImages,
                                     EditDelegateImages as _EditDelegateImages)

from sciplot.ui.models.bars import (TableModelBars as _TableModelBars,
                                     EditDelegateBars as _EditDelegateBars)

from sciplot.data.generic import DataGlobal as _DataGlobal
from sciplot.data.lines import DataLine as _DataLine
from sciplot.data.images import DataImages as _DataImages
from sciplot.data.bars import DataBar as _DataBar
from sciplot.data.special import DataFillBetween as _DataFillBetween

class SciPlotUI(_QMainWindow):
    """
    Scientific plotting user-interface for creating publication-quality plots
    and images

    Parameters
    ----------

    limit_to : list, optional (default = None)
        Limit the application to implement only certain functionality. \
        Default is all elements turned ON. See Notes for options.
        
    show : bool, optional (default = True)
        Whether to show the UI upon instantiation

    Methods
    -------
    plot : MPL-like plotting functionality

    imshow : MPL-like imshow

    bar : MPL-like bar plot EXCEPT centered (rather than left-edge defined)

    hist : MPL-like histogram

    fill_between : MPL-like fill_between

    Internal Methods
    ----------------
    updatePlotDataStyle : Make updates to plots (lines) when a stylistic \
    change is made within the model-table

    updatePlotDataDelete : Remove a plot when deleted from model-table

    updateFillBetweenDataStyle : Make updates to fill between's when a \
    stylistic change is made within the model-table

    updateFillBetweenDataDelete : Remove a fill between when deleted from \
    model-table

    updateImagesDataStyle : Make updates to images when a stylistic \
    change is made within the model-table

    updateImageDataDelete : Remove an image when deleted from model-table

    updateBarsDataStyle : Make updates to bars plots when a stylistic \
    change is made within the model-table

    updateBarsDataDelete : Remove a bar plot when deleted from model-table

    refreshAllPlots : Delete all plots and re-plot

    updateAllLabels : Update all labels (x-, y-, title, etc) on MPL widget, \
        in model, in data container, and in UI lineEdits

    updateLineEditLabels : Update all labels (x-, y-, title, etc) in UI \
        lineEdits

    updateDataLabels : Update all labels (x-, y-, title, etc) in data \
    container

    updateMplLabels : Update all labels (x-, y-, title, etc) on MPL widget

    updateLabelsFromLineEdit : Update all labels (x-, y-, title, etc) on MPL \
        widget, in model, and in data container. Edits came from lineEdits.

    axisAspect : Set MPL-axis aspect ratio setting

    axisScaling : Set MPL-axis scaling ratio setting

    axisVisible : Set MPL-axis on or off

    axisLimits : Set MPL-axis limits

    updateAxisParameters : Query and update UI lineEdits related to axis \
        properties such as limits, visibility (on/off), scaling, and aspect \
        ratio

    Notes
    -----
    * limit_to options: 'lines', 'fill betweens', 'bars', images'
    """
    # Signal emitted when clearAll is called
    # Added for external programs
    all_cleared = _pyqtSignal(int)  
    
    def __init__(self, limit_to=None, parent=None, show=True):
        self.__version__ = sciplot.__version__
        self.list_ids = []
        self.list_all = []

        # There are a number of changes and deprectaion
        # in MPL v2 and v3 ; thus, this will be tracked
        # so MPL 1 and 2 can be used seemlessly
        self._mpl_v1 = int(_mpl.__version__.rsplit('.')[0]) == 1

        # Check to see if QApp already exists
        # if not, one has to be created
        self.app = None
        if _QApplication.instance() is None:
            print('\nNo QApplication instance (this is common with certain \
            version of Matplotlib). Creating one.\n\r\
            You will need to exec manually after you finish plotting.\n\
            -----------Example---------------\n\
            import sciplot\n\
            sp = sciplot.main()\n\n\
            # Plot a line\n\
            sp.plot((0,1),(0,1))\n\n\
            # Start the QApplication\n\
            sp.app.exec_()')
            self.app = _QApplication(_sys.argv)
            self.app.setQuitOnLastWindowClosed(True)

        self.setup(limit_to=limit_to, parent=parent)
        if show:
            self.show()
       
    def closeEvent(self, event):
        pass

    def _tabAvailability(self, limit_to=None):
        """
        If limit_to is provided, limits the tabs (elements) that are available.

        May be useful for built-upon applications.
        """
        if limit_to is None:
            self.elements = ['lines', 'fill betweens', 'images', 'bars']
            self._to_setup = [self.setupLines, self.setupFillBetweens,
                              self.setupImages, self.setupBars]
        else:
            self._to_setup = []
            self.elements = []
            if limit_to.count('lines'):
                self.elements.append('lines')
                self._to_setup.append(self.setupLines)
            if limit_to.count('fill betweens'):
                self.elements.append('fill betweens')
                self._to_setup.append(self.setupFillBetweens)
            if limit_to.count('images'):
                self.elements.append('images')
                self._to_setup.append(self.setupImages)
            if limit_to.count('bars'):
                self.elements.append('bars')
                self._to_setup.append(self.setupBars)

    def setupLines(self):
        """
        Enable and setup line plotting
        """

        # Enable line plotting
        self.plot = self.__plot
        self.updatePlotDataStyle = self.__updatePlotDataStyle
        self.updatePlotDataDelete = self.__updatePlotDataDelete

        # Initial  and insert table view for line plots
        self.tableViewLine = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewLine, 'Lines')

        # Set model and delegates
        # Lines
        self.modelLine = _TableModelLines()
        self.delegateLine = _EditDelegateLines()
        self.tableViewLine.setModel(self.modelLine)
        self.tableViewLine.setItemDelegate(self.delegateLine)
        self.tableViewLine.show()


        # RESIZE COLUMNS
        header = self.tableViewLine.horizontalHeader()
        # alpha
        col = self.modelLine._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # linewidth
        col = self.modelLine._COL_LINEWIDTH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # markersize
        col = self.modelLine._COL_MARKERSIZE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # delete
        col = self.modelLine._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewLine.doubleClicked.connect(
            self.modelLine.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelLine.dataChanged.connect(self.updatePlotDataStyle)
        self.modelLine.dataDeleted.connect(self.updatePlotDataDelete)

        # Export lines to csv
        self.ui.actionExport_Lines_to_CSV.setVisible(True)
        self.ui.actionExport_Lines_to_CSV.triggered.connect(self.export_lines_csv)

    def setupFillBetweens(self):
        """
        Enable and setup fill between plotting
        """

        # Enable fill_between plotting
        self.fill_between = self.__fill_between
        self.updateFillBetweenDataStyle = self.__updateFillBetweenDataStyle
        self.updateFillBetweenDataDelete = self.__updateFillBetweenDataDelete

        # Initial and insert table view for fill_between plots
        self.tableViewFillBetween = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewFillBetween,
                                      'Fill Between')

        # Fill Between
        self.modelFillBetween = _TableModelFillBetween()
        self.delegateFillBetween = _EditDelegateFillBetween()
        self.tableViewFillBetween.setModel(self.modelFillBetween)
        self.tableViewFillBetween.setItemDelegate(self.delegateFillBetween)
        self.tableViewFillBetween.show()

        # RESIZE COLUMNS
        header = self.tableViewFillBetween.horizontalHeader()
        # alpha
        col = self.modelFillBetween._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewFillBetween.setColumnWidth(col, new_width)

        # linewidth
        col = self.modelFillBetween._COL_LINEWIDTH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewFillBetween.setColumnWidth(col, new_width)

        # delete
        col = self.modelFillBetween._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewFillBetween.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewFillBetween.doubleClicked.connect(
            self.modelFillBetween.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelFillBetween.dataChanged.connect(self.updateFillBetweenDataStyle)
        self.modelFillBetween.dataDeleted.connect(self.updateFillBetweenDataDelete)

        # Export fillbetweens to csv
        self.ui.actionExport_Fill_Between_to_CSV.setVisible(True)
        self.ui.actionExport_Fill_Between_to_CSV.triggered.connect(self.export_fillbetweens_csv)

    def setupImages(self):
        """
        Enable and setup image plotting
        """

        # Enable imaging
        self.imshow = self.__imshow
        self.updateImagesDataStyle = self.__updateImagesDataStyle
        self.updateImagesDataDelete = self.__updateImagesDataDelete

        # images data-- similar to plot_data above

        # Initial  and insert table view for images
        self.tableViewImages = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewImages, 'Images')

        # Images
        self.modelImages = _TableModelImages()
        self.delegateImages = _EditDelegateImages()
        self.tableViewImages.setModel(self.modelImages)
        self.tableViewImages.setItemDelegate(self.delegateImages)
        self.tableViewImages.show()

        # RESIZE COLUMNS
        header = self.tableViewImages.horizontalHeader()
        # alpha
        col = self.modelImages._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # clim low
        col = self.modelImages._COL_CLIM_LOW
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # clim high
        col = self.modelImages._COL_CLIM_HIGH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # delete
        col = self.modelImages._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewImages.doubleClicked.connect(
            self.modelImages.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelImages.dataChanged.connect(self.updateImagesDataStyle)
        self.modelImages.dataDeleted.connect(self.updateImagesDataDelete)

    def setupBars(self):
        """
        Enable and setup bar and histogram plotting
        """

        # Enable bar plotting
        self.bar = self.__bar
        self.hist = self.__hist
        self.updateBarsDataStyle = self.__updateBarsDataStyle
        self.updateBarsDataDelete = self.__updateBarsDataDelete

        # Initial  and insert table view for bars
        self.tableViewBars = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewBars, 'Bars')


        # Bars/Bars
        self.modelBars = _TableModelBars()
        self.delegateBars = _EditDelegateBars()
        self.tableViewBars.setModel(self.modelBars)
        self.tableViewBars.setItemDelegate(self.delegateBars)
        self.tableViewBars.show()

        # RESIZE COLUMNS
        header = self.tableViewBars.horizontalHeader()
        # alpha
        col = self.modelBars._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # linewidth
        col = self.modelBars._COL_LINEWIDTH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # widthfactor
        col = self.modelBars._COL_WIDTH_FACTOR
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # delete
        col = self.modelBars._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewBars.doubleClicked.connect(
            self.modelBars.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelBars.dataChanged.connect(self.updateBarsDataStyle)
        self.modelBars.dataDeleted.connect(self.updateBarsDataDelete)

        # Export bars to csv
        self.ui.actionExport_Bars_to_CSV.setVisible(True)
        self.ui.actionExport_Bars_to_CSV.triggered.connect(self.export_bars_csv)

    def setup(self, limit_to=None, parent=None):
        """
        Basic UI setup
        """

        # Generic start to any pyQT program
        
        
        super(SciPlotUI, self).__init__(parent)
        self.ui = Ui_Plotter()
        self.ui.setupUi(self)
        self.setSizePolicy(_QSizePolicy.Expanding,
                           _QSizePolicy.Expanding)

        # Global "data" i.e., title, x-label, y-label, etc
        self._global_data = _DataGlobal()

        # MPL plot widget
        self.mpl_widget = _MplCanvas(height=6, dpi=100)

        # Hold is deprecated in MPL2
        if self._mpl_v1:
            self.mpl_widget.ax.hold(True)

        # Insert MPL widget and toolbar
        self.ui.verticalLayout.insertWidget(0, self.mpl_widget, 0, _QtCore.Qt.AlignHCenter)
        
        self.ui.verticalLayout.insertWidget(0, self.mpl_widget.toolbar,0, _QtCore.Qt.AlignHCenter)
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

        # Insert TabWidget
        self.ui.modelTabWidget = _QTabWidget()
        self.ui.verticalLayout.insertWidget(-1, self.ui.modelTabWidget)

        # Setup what tabs are available:
        self._tabAvailability(limit_to)
        for count in self._to_setup:
            count()

        # SIGNALS AND SLOTS
        
        # Global labels
        self.ui.lineEditTitle.editingFinished.connect(self.updateLabelsFromLineEdit)
        self.ui.lineEditXLabel.editingFinished.connect(self.updateLabelsFromLineEdit)
        self.ui.lineEditYLabel.editingFinished.connect(self.updateLabelsFromLineEdit)

        # Non-tracked (not saved) properties
        self.ui.comboBoxAspect.currentIndexChanged.connect(self.axisAspect)
        self.ui.comboBoxAxisScaling.currentIndexChanged.connect(self.axisScaling)
        self.ui.checkBoxAxisVisible.stateChanged.connect(self.axisVisible)
        self.ui.lineEditXLimMin.editingFinished.connect(self.axisLimits)
        self.ui.lineEditXLimMax.editingFinished.connect(self.axisLimits)
        self.ui.lineEditYLimMin.editingFinished.connect(self.axisLimits)
        self.ui.lineEditYLimMax.editingFinished.connect(self.axisLimits)
        
        # Actions
        self.ui.pushButtonClearAll.pressed.connect(self.clearAll)
        self.ui.pushButtonDefaultView.pressed.connect(self.defaultView)

        # Formatting Features
        self.ui.actionFigureDPI.triggered.connect(self.figureDPI)
        self.ui.actionFigureSavedDPI.triggered.connect(self.figureSaveDPI)
        self.ui.actionFigure_Size_Display.triggered.connect(self.figureSizeDisplay)
        self.ui.pushButtonApplyFigParams.pressed.connect(self.applyFigProps)

    def __plot(self, x, y, label=None, x_label=None, y_label=None, meta={}, 
               **kwargs):
        """
        MPL-like plotting functionality

        Parameters
        ----------
        x : ndarray (1D)
            X-axis data

        y : ndarray (1D, for now)
            Y-axis data

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-plot

        """

        # Temporary plot-data
        plot_data = _DataLine()
        plot_data.x = x
        plot_data.y = y
        plot_data.label = label
        plot_data.meta = meta
        plot_data.id = _time.time()

        # Plot outputs a line object
        plot_data.mplobj = self.mpl_widget.ax.plot(x, y, label=label, **kwargs)
        try:
            self.mpl_widget.ax.legend(loc='best')
        except:
            pass

        # If labels are provided, update the global data and the linEdits
        if x_label is not None or y_label is not None:
            self.updateAllLabels(x_label=x_label, y_label=y_label)

        self.mpl_widget.fig.tight_layout()
        self.axisAspect()
        self.mpl_widget.draw()


        # Since the plot was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup color, linewidth, etc.
        # Thus, we plot, then retrieve what the style info was
        plot_data.retrieve_style_from_line(plot_data.mplobj[0])

        # Append this specific plot data to out list of all plots
        self.list_ids.append(plot_data.id)
        self.list_all.append(plot_data)

        # Update model
        self.modelLine._model_data.append(plot_data.model_style)
        self.modelLine.layoutChanged.emit()

    def updateMplLabels(self, x_label=None, y_label=None, title=None):
        """
        Within the MPL widget, update the x- and y-labels and the title
        """
        if x_label is not None:
            self.mpl_widget.ax.set_xlabel(x_label)

        if y_label is not None:
            self.mpl_widget.ax.set_ylabel(y_label)

        if title is not None:
            self.mpl_widget.ax.set_title(title)
        self.mpl_widget.fig.tight_layout()
        self.mpl_widget.draw()

    def updateDataLabels(self, x_label=None, y_label=None, title=None):
        """
        Within the global data container, update the x- and y-labels and the \
        title
        """
        if x_label is not None:
            self._global_data.labels['x_label'] = x_label

        if y_label is not None:
            self._global_data.labels['y_label'] = y_label

        if title is not None:
            self._global_data.labels['title'] = title

    def updateLineEditLabels(self, x_label=None, y_label=None, title=None):
        """
        Within the pyQT lineEdit widgets, update the x- and y-labels and the \
        title
        """
        if x_label is not None:
            self.ui.lineEditXLabel.setText(x_label)

        if y_label is not None:
            self.ui.lineEditYLabel.setText(y_label)

        if title is not None:
            self.ui.lineEditTitle.setText(title)

    def updateAllLabels(self, x_label=None, y_label=None, title=None):
        """
        Update the x- and y-labels and the title in the MPL widget, the \
        lineEdit boxes, and the global data container
        """
        self.updateMplLabels(x_label=x_label, y_label=y_label, title=title)
        self.updateDataLabels(x_label=x_label, y_label=y_label, title=title)
        self.updateLineEditLabels(x_label=x_label, y_label=y_label,
                                  title=title)

    def updateLabelsFromLineEdit(self):
        """
        From the linEdit widgets, update the x- and y-labels and the title \
        in the MPL widget and the global data container
        """
        title = None
        x_label = None
        y_label = None

        sender = self.sender()
        if sender == self.ui.lineEditTitle:
            title = self.ui.lineEditTitle.text()
        elif sender == self.ui.lineEditXLabel:
            x_label = self.ui.lineEditXLabel.text()
        elif sender == self.ui.lineEditYLabel:
            y_label = self.ui.lineEditYLabel.text()
        self.updateDataLabels(x_label=x_label, y_label=y_label, title=title)
        self.updateMplLabels(x_label=x_label, y_label=y_label, title=title)

    def __updatePlotDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the plot data
        """
        for num, style_info in enumerate(self.modelLine._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updatePlotDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        try:
            idx_to_remove = self.list_ids.index(plt_id)
            self.list_ids.pop(idx_to_remove)
            self.list_all.pop(idx_to_remove)
        except:
            print('Error in __updatePlotDataDelete: {}'.format(idx_to_remove))
            
        self.refreshAllPlots()

    def refreshAllPlots(self):
        """
        Clear and re-plot all plot data of all types
        """

        # Clear axis -- in the future, maybe clear figure and recreate axis
        self.mpl_widget.ax.clear()
        
        for itm in self.list_all:
            if isinstance(itm, _DataLine):
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                itm.mplobj = self.mpl_widget.ax.plot(itm.x, itm.y, 
                                                     label=label,
                                                     color=itm.style_dict['color'],
                                                     alpha=itm.style_dict['alpha'],
                                                     linewidth=itm.style_dict['linewidth'],
                                                     linestyle=itm.style_dict['linestyle'],
                                                     marker=itm.style_dict['marker'],
                                                     markersize=itm.style_dict['markersize'])
            elif isinstance(itm, _DataBar):
#                print('Bar')
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                itm.mplobj = self.mpl_widget.ax.bar(itm._left, itm.y, 
                                                    bottom=itm.bottom,
                                                    width=itm._width,
                                                    label=label,
                                                    facecolor=itm.style_dict['facecolor'],
                                                    alpha=itm.style_dict['alpha'],
                                                    edgecolor=itm.style_dict['edgecolor'],
                                                    linewidth=itm.style_dict['linewidth'])
            elif isinstance(itm, _DataImages):
#                print('Images')
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                if itm.cbar['obj'] is not None:
                    try:  # Have had some unknown exceptions with .remove()
                        itm.cbar['obj'].remove()
                        itm.cbar['obj'] = None
                    except:
                        pass
                itm.mplobj = self.mpl_widget.ax.imshow(itm.img, label=label,
                                                       interpolation='none',
                                                       origin='lower',
                                                       cmap=_mpl.cm.cmap_d[itm.style_dict['cmap_name']],
                                                       alpha=itm.style_dict['alpha'],
                                                       clim=itm.style_dict['clim'])
                if itm.cbar['show']:
                    itm.cbar['obj'] = self.mpl_widget.fig.colorbar(itm.mplobj,
                                                                   use_gridspec=True)
            elif isinstance(itm, _DataFillBetween):
#                print('Fill Between')
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                itm.mplobj = self.mpl_widget.ax.fill_between(itm.x, itm.y_low, itm.y_high,
                                                  label=label,
                                                  facecolor=itm.style_dict['facecolor'],
                                                  edgecolor=itm.style_dict['edgecolor'],
                                                  alpha=itm.style_dict['alpha'],
                                                  linewidth=itm.style_dict['linewidth'])

            else:
                print('Unknown')

        # Only add legend if legend handles exist
        h,l = self.mpl_widget.ax.get_legend_handles_labels()
        if l:
            self.mpl_widget.ax.legend(loc='best')

        # Apply x- and y-labels and a title if they are set
        if self._global_data.labels['title'] is not None:
            self.mpl_widget.ax.set_title(self._global_data.labels['title'])
        if self._global_data.labels['x_label'] is not None:
            self.mpl_widget.ax.set_xlabel(self._global_data.labels['x_label'])
        if self._global_data.labels['y_label'] is not None:
            self.mpl_widget.ax.set_ylabel(self._global_data.labels['y_label'])

        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.axisAspect()
        self.mpl_widget.draw()

    def __fill_between(self, x, y_low, y_high, label=None,  meta={},
                       x_label=None, y_label=None, **kwargs):
        """
        MPL-like fill_between plotting functionality

        Parameters
        ----------
        x : ndarray (1D)
            X-axis data

        y_low : ndarray (1D, for now)
            Low Y-axis data

        y_high : ndarray (1D, for now)
            High Y-axis data

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-fill_between

        """

        # Temporary fill_between-data
        fill_between_data = _DataFillBetween()
        fill_between_data.x = x
        fill_between_data.y_low = y_low
        fill_between_data.y_high = y_high
        fill_between_data.label = label
        fill_between_data.meta = meta
        fill_between_data.id = _time.time()

        # Fill between outputs a polycollection
        fill_between_data.mplobj = self.mpl_widget.ax.fill_between(x, y_low, y_high,
                                                   label=label, **kwargs)
        self.mpl_widget.ax.legend(loc='best')
        self.mpl_widget.fig.tight_layout()
        self.axisAspect()
        self.mpl_widget.draw()

        # Since the fill_between was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup color, linewidth, etc.
        # Thus, we plot, then retrieve what the style info was
        fill_between_data.retrieve_style_from_polycollection(fill_between_data.mplobj)

        # Append this specific plot data to out list of all plots
        self.list_ids.append(fill_between_data.id)
        self.list_all.append(fill_between_data)

        # Update model
        self.modelFillBetween._model_data.append(fill_between_data.model_style)
        self.modelFillBetween.layoutChanged.emit()
        

    def __updateFillBetweenDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the fill_between data
        """
        for num, style_info in enumerate(self.modelFillBetween._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updateFillBetweenDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        idx_to_remove = self.list_ids.index(plt_id)
        self.list_ids.pop(idx_to_remove)
        self.list_all.pop(idx_to_remove)
        
        self.refreshAllPlots()

    def __imshow(self, img, x=None, y=None, label=None, meta={},
               x_label=None, y_label=None, cbar=False, **kwargs):
        """
        MPL-like plotting functionality

        Parameters
        ----------
        img : ndarray (2D)
            Image data

        x : ndarray (1D)
            X-axis data

        y : ndarray (1D, for now)
            Y-axis data

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)
            
        cbar : bool
            Attach a colorbar to the img

        kwargs : dict
            Other parameters sent directly to mpl-imshow

        """

        # Temporary plot-data
        image_data = _DataImages()
        image_data.img = img
        image_data.x = x
        image_data.y = y
        image_data.label = label
        image_data.meta = meta
        image_data.id = _time.time()
        image_data.cbar['show'] = cbar

        # Imshow outputs an image object
        image_data.mplobj = self.mpl_widget.ax.imshow(img, interpolation='None',
                                                origin='lower',
                                                label=label,
                                                **kwargs)
        if image_data.cbar['show']:
            image_data.cbar['obj'] = self.mpl_widget.fig.colorbar(image_data.mplobj,
                                                use_gridspec=True)
            
#        self.mpl_widget.ax.legend(loc='best')

        # If labels are provided, update the global data and the linEdits
        if x_label is not None or y_label is not None:
            self.updateAllLabels(x_label=x_label, y_label=y_label)

        self.mpl_widget.fig.tight_layout()
        
        self.axisAspect()
        self.mpl_widget.draw()

        # Since the image was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup cmap, etc.
        # Thus, we plot, then retrieve what the style info was
        image_data.retrieve_style_from_image(image_data.mplobj)

        # Append this specific plot data to out list of all plots
        self.list_ids.append(image_data.id)
        self.list_all.append(image_data)

        # Update model
        self.modelImages._model_data.append(image_data.model_style)
        self.modelImages.layoutChanged.emit()


    def __updateImagesDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the fill_between data
        """
        for num, style_info in enumerate(self.modelImages._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updateImagesDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        
        idx_to_remove = self.list_ids.index(plt_id)
        self.list_ids.pop(idx_to_remove)
        popd = self.list_all.pop(idx_to_remove)
            
        if popd.cbar['obj'] is not None:
            popd.cbar['obj'].remove()
#        self.axisAspect()
        self.refreshAllPlots()


    def __bar(self, x, y, bottom=0, width_factor=1.0, use_real_width=False, 
              label=None, meta={}, x_label=None, y_label=None, **kwargs):
        """
        MPL-like plotting functionality

        Note
        ----
        Unlike MPL bar, this method uses centered data. Thus, x is the center \
        position of the bar

        Parameters
        ----------
        x : ndarray (1D)
            X-axis data (center of bars)

        y : ndarray (1D, for now)
            Y-axis data (height)

        bottom : float (for now)
            Baseline of bars
            
        width_factor: float
            If legnth of y>1, fraction of space between bars taken up by bar \
            (e.g. 1.0 leads to bars that tough). If y is a single-value OR \
            use_real_width is True), is the width of the bar.

        use_real_width : bool, optional (default=False):
            If True, width_factor is the real width (in x-units)
            
        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-plot

        """

        # Temporary plot-data
        bar_data = _DataBar()
        bar_data.x = x
        bar_data.y = y
        bar_data.bottom = bottom
        bar_data.label = label
        bar_data.meta = meta
        bar_data.id = _time.time()

        bar_data.style_dict['width_factor'] = width_factor

        _multi_value = None

        if isinstance(y, (int, float)):
            _multi_value = False
        if isinstance(y, _np.ndarray):
            if y.size == 1:
                _multi_value = False
            else:
                _multi_value = True
        if isinstance(y, (list, tuple)):
            if len(y) == 1:
                _multi_value = False
            else:
                _multi_value = True

        if _multi_value and use_real_width == False:
            # Distance between bars
            bar_data._gap = _np.abs(x[1]-x[0])

            # Width of a bar is a fraction of the gap
            bar_data._width = bar_data._gap*bar_data.style_dict['width_factor']

        else:
            # Single-valued: no gap
            bar_data._gap = None
            bar_data._width = width_factor

        # MPL-bar uses left-edge rather than center
        bar_data._left = bar_data.x - bar_data._width/2

        # Plot outputs a list of patch objects
        bar_data.mplobj = self.mpl_widget.ax.bar(bar_data._left, y,
                                           bottom=bar_data.bottom,
                                           width=bar_data._width,
                                           label=label, **kwargs)
        self.mpl_widget.ax.legend(loc='best')

        # If labels are provided, update the global data and the linEdits
        if x_label is not None or y_label is not None:
            self.updateAllLabels(x_label=x_label, y_label=y_label)

        self.mpl_widget.fig.tight_layout()
        
        self.axisAspect()
        self.mpl_widget.draw()


        # Since the plot was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup color, linewidth, etc.
        # Thus, we plot, then retrieve what the style info was
        bar_data.retrieve_style_from_bar(bar_data.mplobj[0])
    
        # Append this specific plot data to out list of all plots
        self.list_ids.append(bar_data.id)
        self.list_all.append(bar_data)
        
        # Update model
        self.modelBars._model_data.append(bar_data.model_style)
        self.modelBars.layoutChanged.emit()
        
        # Note: New in MPL2, edgecolor is RGBA with A defaulting to 0
        # (ie transparent, which Sciplot does not currently support).
        self.refreshAllPlots()

    def __hist(self, data, bins=10, label=None, meta={}, x_label=None,
             y_label='Counts', **kwargs):
        """
        MPL-like histogram plotting

        Parameters
        ----------
        data : ndarray (1D, for now)
            Data (center of bars)

        bins : int
            Number of histogram bins

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-plot

        """
        counts, lefts = _np.histogram(data, bins=bins)
        gap = _np.abs(lefts[1] - lefts[0])
        offset = gap/2

        self.bar(lefts[:-1]+offset, counts, width_factor=1.0, label=label,
                 x_label=x_label, y_label=y_label, meta=meta, **kwargs)

    def __updateBarsDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the fill_between data
        """
        for num, style_info in enumerate(self.modelBars._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updateBarsDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        idx_to_remove = self.list_ids.index(plt_id)
        self.list_ids.pop(idx_to_remove)
        self.list_all.pop(idx_to_remove)
        
        self.refreshAllPlots()

    def axisAspect(self):
        """
        Set axis aspect ratio property
        """
        aspect = self.ui.comboBoxAspect.currentText()
        self.mpl_widget.ax.set_aspect(aspect)
        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def axisScaling(self):
        """
        Set axis scaling property
        """
        ratio = self.ui.comboBoxAxisScaling.currentText()
        self.mpl_widget.ax.axis(ratio)
        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def axisVisible(self):
        """
        Set whether axis is on or off
        """
        state = self.ui.checkBoxAxisVisible.isChecked()
        if state:
            state = 'on'
        else:
            state = 'off'

        self.mpl_widget.ax.axis(state)
        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def axisLimits(self):
        """
        Set axis limits
        """
        if self.sender() == self.ui.lineEditXLimMin:
            value = float(self.ui.lineEditXLimMin.text())
            self.mpl_widget.ax.axis(xmin=value)
        elif self.sender() == self.ui.lineEditXLimMax:
            value = float(self.ui.lineEditXLimMax.text())
            self.mpl_widget.ax.axis(xmax=value)
        elif self.sender() == self.ui.lineEditYLimMin:
            value = float(self.ui.lineEditYLimMin.text())
            self.mpl_widget.ax.axis(ymin=value)
        elif self.sender() == self.ui.lineEditYLimMax:
            value = float(self.ui.lineEditYLimMax.text())
            self.mpl_widget.ax.axis(ymax=value)

        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def updateAxisParameters(self):
        """
        Query current state of axis settings and update appropriate lineEdit's
        """
        axis_visible = self.mpl_widget.ax.axison
        self.ui.checkBoxAxisVisible.setChecked(axis_visible)
        xmin, xmax, ymin, ymax = self.mpl_widget.ax.axis()
        self.ui.lineEditXLimMin.setText(str(round(xmin,5)))
        self.ui.lineEditXLimMax.setText(str(round(xmax,5)))
        self.ui.lineEditYLimMin.setText(str(round(ymin,5)))
        self.ui.lineEditYLimMax.setText(str(round(ymax,5)))

    def updateFigureParameters(self):
        """
        Query current state of axis settings and update appropriate lineEdit's
        """
        fig_dpi = self.mpl_widget.fig.get_dpi()
        save_dpi = _mpl.rcParams['savefig.dpi']
        if save_dpi is 'figure':
            save_dpi = fig_dpi
        fig_size = self.mpl_widget.fig.get_size_inches()
        
        self.ui.spinBoxFigureDPI.setValue(fig_dpi)
        self.ui.spinBoxFigureSavedDPI.setValue(save_dpi)
        self.ui.spinBoxFigSizeWidth.setValue(fig_size[0])
        self.ui.spinBoxFigSizeHeight.setValue(fig_size[1])
        
    def defaultView(self):
        """
        Set default and Home view to the current one
        """
        # New versions of MPL don't have these functions
        try:
            self.mpl_widget.toolbar._views.clear()
            self.mpl_widget.toolbar._positions.clear()
        except:
            pass

        self.mpl_widget.toolbar.update()
    
    def clearAllBars(self):
        try:
            self.modelBars._model_data = []
            ids = self.list_bar_ids
            for i in ids:
                self.clearID(i)
            
            self.modelBars.layoutChanged.emit()
        except:
            print('Error in clearAllBars')

    def clearID(self, clear_id):
        idx_to_remove = self.list_ids.index(clear_id)
        self.list_ids.pop(idx_to_remove)
        self.list_all.pop(idx_to_remove)

    def clearAll(self):
        """
        Clear all plots and graphs and images
        """
        try:
            self.modelLine._model_data = []
            self.modelLine.layoutChanged.emit()
        except:
            print('Error in clear all of plots/lines')
        
        try:
            self.modelBars._model_data = []
            self.modelBars.layoutChanged.emit()
        except:
            print('Error in clear all of bars')
        
        
        try:
            # Need to iterate as to check for colorbar existance
            for num, model_data in enumerate(self.modelImages._model_data):
                idx_to_remove = self.list_ids.index(model_data['id'])
                self.list_ids.pop(idx_to_remove)
                popd = self.list_all.pop(idx_to_remove)
                if popd.cbar['obj'] is not None:
                    popd.cbar['obj'].remove()
            
            self.modelImages._model_data = []
            self.modelImages.layoutChanged.emit()
        except:
            print('Error in clear all of images')
        
        try:
            self.modelFillBetween._model_data = []
            self.modelFillBetween.layoutChanged.emit()
        except:
            print('Error in clear all of fill-betweens')
            
        try:
            self.list_ids = []
            self.list_all = []
        except:
            print('Error in clear all')
        finally:
            self.refreshAllPlots()
            self.all_cleared.emit(id(self))

    def export_bars_csv(self):
        ret = _QFileDialog.getSaveFileName(filter="Comma-Separated Values (*.csv);;All Files (*.*)")
        if ret[0]:
            # pth, fname = _os.path.split(ret[0])
            with open(ret[0],'w') as f:
                for q in self.list_bar_objs:
                    f.write('{}\n'.format(q.label))
                    f.write('left,')
                    q._left.tofile(f, sep=',')
                    f.write('\nx,')
                    q.x.tofile(f, sep=',')
                    f.write('\ny,')
                    q.y.tofile(f,sep=',')
                    f.write('\n\n')    

    def export_lines_csv(self):
        ret = _QFileDialog.getSaveFileName(filter="Comma-Separated Values (*.csv);;All Files (*.*)")
        if ret[0]:
            # pth, fname = _os.path.split(ret[0])
            with open(ret[0],'w') as f:
                for q in self.list_line_objs:
                    f.write('{}\n'.format(q.label))
                    f.write('x,')
                    q.x.tofile(f, sep=',')
                    f.write('\ny,')
                    q.y.tofile(f,sep=',')
                    f.write('\n\n')

    def export_fillbetweens_csv(self):
        ret = _QFileDialog.getSaveFileName(filter="Comma-Separated Values (*.csv);;All Files (*.*)")
        if ret[0]:
            # pth, fname = _os.path.split(ret[0])
            with open(ret[0],'w') as f:
                for q in self.list_fillbetween_objs:
                    f.write('{}\n'.format(q.label))
                    f.write('x,')
                    q.x.tofile(f, sep=',')
                    f.write('\ny_low,')
                    q.y_low.tofile(f,sep=',')
                    f.write('\ny_high,')
                    q.y_high.tofile(f,sep=',')
                    f.write('\n\n')

    @property
    def n_lines(self):
        return sum(isinstance(x, _DataLine) for x in self.list_all)
    
    @property
    def n_bars(self):
        return sum(isinstance(x, _DataBar) for x in self.list_all)
        
    @property
    def n_fillbetweens(self):
        return sum(isinstance(x, _DataFillBetween) for x in self.list_all)
        
    @property
    def n_images(self):
        return sum(isinstance(x, _DataImages) for x in self.list_all)
        
    @property
    def list_line_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataLine)]
        
    @property
    def list_line_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataLine)]
        
    @property
    def list_bar_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataBar)]
        
    @property
    def list_bar_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataBar)]
        
    @property
    def list_fillbetween_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataFillBetween)]
        
    @property
    def list_fillbetween_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataFillBetween)]
        
    @property
    def list_image_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataImages)]
        
    @property
    def list_image_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataImages)]
        
    def figureDPI(self):
        curr_dpi = self.mpl_widget.fig.get_dpi()
        dpi, okPressed = _QInputDialog.getInt(self, "New Figure DPI","DPI:", curr_dpi, 10, 100000, 25)
        if okPressed:
            self.mpl_widget.fig.set_dpi(dpi)
            self.mpl_widget.updateGeometry()
            self.mpl_widget.draw()
        self.updateFigureParameters()

    def figureSaveDPI(self):
        curr_dpi = _mpl.rcParams['savefig.dpi']
        if not isinstance(curr_dpi, int):
            # Savefig is set to 'figure'
            curr_dpi = self.mpl_widget.fig.get_dpi()
        
        dpi, okPressed = _QInputDialog.getInt(self, "New DPI for Saved Figures","DPI:", curr_dpi, 10, 100000, 25)
        if okPressed:
            _mpl.rcParams['savefig.dpi'] = dpi
        self.updateFigureParameters()

    def figureSizeDisplay(self):
        curr_size = self.mpl_widget.fig.get_size_inches()
        new_size, okPressed = DualEntry.getDualEntries(curr_size[0], curr_size[1], input_type=float, text="Figure Size (W x H inches)", parent=self)
        if okPressed:
            self.mpl_widget.updateGeometry()
            self.mpl_widget.fig.set_size_inches(new_size[0], new_size[1], forward=True)
            self.mpl_widget.draw()
        self.updateFigureParameters()

    def applyFigProps(self):
        """ Apply manually-entered figure properties (e.g., dpi) """
        
        new_fig_size = (self.ui.spinBoxFigSizeWidth.value(), self.ui.spinBoxFigSizeHeight.value())
        new_fig_dpi = self.ui.spinBoxFigureDPI.value()
        new_save_dpi = self.ui.spinBoxFigureSavedDPI.value()

        self.mpl_widget.fig.set_dpi(new_fig_dpi)
        self.mpl_widget.fig.set_size_inches(new_fig_size)
        _mpl.rcParams['savefig.dpi'] = new_save_dpi
        self.mpl_widget.updateGeometry()
        self.mpl_widget.draw()

if __name__ == '__main__':

    app = _QApplication(_sys.argv)
    #app.setQuitOnLastWindowClosed(True)
    
    winPlotter = SciPlotUI(limit_to=['lines','bars', 'fill betweens',
                                     'images'])
    #winPlotter.show()

    x = _np.arange(100)
    y = x**2

    winPlotter.plot(x, y, x_label='X', label='Plot')
#    winPlotter.plot(x, y**1.1, label='Plot 2')
    winPlotter.fill_between(x, y-1000, y+1000, label='Fill Between')
#
    winPlotter.imshow(_np.random.randn(100,100), label='Imshow', cbar=True)
    winPlotter.bar(x[::10],y[::10],label='Bar')
#    winPlotter.hist(y,label='Hist')

#    winPlotter.bar(0,10, label='Bar: single-value')
    app.exec_()
<filename>Lib/site-packages/sciplot/sciplotUI.py
# -*- coding: utf-8 -*-
"""
SciPlot-PyQt: Publication-ready scientific plotting for Python
==============================================================

SciPlot-PyQt (aka SciPlot) is a user-interface/matplotlib wrapper built with 
PyQt5 that allows interactive plotting through an embedded matplotlib canvas. 
It enables fast and easy publication-ready plots and images:
    
    * Interactive plotting
    * Theme and style editing (TODO)
    * Figure saving and opening for later editing (TODO)
    
Supported Plot Types
---------------------
Line plots : plot

Bar plots : bar, hist

Polycollections : fill_between

Images : imshow

Notes
-----
SciPlot has a lot of advances/improvements to make. Feel free to contact me--
help is always welcome!

Usage
-----
import sciplot
sp = sciplot.SciPlotUI()
sp.show()

Example
-------
sp.plot((0,1),(2,3),label='Line', x_label='X', y_label='Y', ls='--')
sp.fill_between((0,1),(1,2),(3,4),label='Fill Between', color='r', alpha=0.25)

Authors
-------
* <NAME>. <<EMAIL>>
"""

import sys as _sys
import os as _os
import numpy as _np
import time as _time

# Generic imports for MPL-incorporation
import matplotlib as _mpl
_mpl.use('Qt5Agg')
import matplotlib.pyplot as _plt  # Simply for it starting a QApplication

# Generic imports for QT-based programs
from PyQt5.QtWidgets import (QApplication as _QApplication,
                             QMainWindow as _QMainWindow,
                             QTableView as _QTableView,
                             QSizePolicy as _QSizePolicy,
                             QTabWidget as _QTabWidget,
                             QFileDialog as _QFileDialog,
                             QInputDialog as _QInputDialog)

from PyQt5.QtCore import pyqtSignal as _pyqtSignal
import PyQt5.QtCore as _QtCore

import sciplot

# Import from Designer-based GUI
from sciplot.ui.qt_Plotter import Ui_MainWindow as Ui_Plotter
from sciplot.ui.widget_mpl import MplCanvas as _MplCanvas
from sciplot.ui.dialogs import DualEntry

from sciplot.ui.models.lines import (TableModelLines as _TableModelLines,
                                     EditDelegateLines as _EditDelegateLines)

from sciplot.ui.models.fillbetween import (TableModelFillBetween as
                                               _TableModelFillBetween,
                                           EditDelegateFillBetween as
                                           _EditDelegateFillBetween)

from sciplot.ui.models.images import (TableModelImages as _TableModelImages,
                                     EditDelegateImages as _EditDelegateImages)

from sciplot.ui.models.bars import (TableModelBars as _TableModelBars,
                                     EditDelegateBars as _EditDelegateBars)

from sciplot.data.generic import DataGlobal as _DataGlobal
from sciplot.data.lines import DataLine as _DataLine
from sciplot.data.images import DataImages as _DataImages
from sciplot.data.bars import DataBar as _DataBar
from sciplot.data.special import DataFillBetween as _DataFillBetween

class SciPlotUI(_QMainWindow):
    """
    Scientific plotting user-interface for creating publication-quality plots
    and images

    Parameters
    ----------

    limit_to : list, optional (default = None)
        Limit the application to implement only certain functionality. \
        Default is all elements turned ON. See Notes for options.
        
    show : bool, optional (default = True)
        Whether to show the UI upon instantiation

    Methods
    -------
    plot : MPL-like plotting functionality

    imshow : MPL-like imshow

    bar : MPL-like bar plot EXCEPT centered (rather than left-edge defined)

    hist : MPL-like histogram

    fill_between : MPL-like fill_between

    Internal Methods
    ----------------
    updatePlotDataStyle : Make updates to plots (lines) when a stylistic \
    change is made within the model-table

    updatePlotDataDelete : Remove a plot when deleted from model-table

    updateFillBetweenDataStyle : Make updates to fill between's when a \
    stylistic change is made within the model-table

    updateFillBetweenDataDelete : Remove a fill between when deleted from \
    model-table

    updateImagesDataStyle : Make updates to images when a stylistic \
    change is made within the model-table

    updateImageDataDelete : Remove an image when deleted from model-table

    updateBarsDataStyle : Make updates to bars plots when a stylistic \
    change is made within the model-table

    updateBarsDataDelete : Remove a bar plot when deleted from model-table

    refreshAllPlots : Delete all plots and re-plot

    updateAllLabels : Update all labels (x-, y-, title, etc) on MPL widget, \
        in model, in data container, and in UI lineEdits

    updateLineEditLabels : Update all labels (x-, y-, title, etc) in UI \
        lineEdits

    updateDataLabels : Update all labels (x-, y-, title, etc) in data \
    container

    updateMplLabels : Update all labels (x-, y-, title, etc) on MPL widget

    updateLabelsFromLineEdit : Update all labels (x-, y-, title, etc) on MPL \
        widget, in model, and in data container. Edits came from lineEdits.

    axisAspect : Set MPL-axis aspect ratio setting

    axisScaling : Set MPL-axis scaling ratio setting

    axisVisible : Set MPL-axis on or off

    axisLimits : Set MPL-axis limits

    updateAxisParameters : Query and update UI lineEdits related to axis \
        properties such as limits, visibility (on/off), scaling, and aspect \
        ratio

    Notes
    -----
    * limit_to options: 'lines', 'fill betweens', 'bars', images'
    """
    # Signal emitted when clearAll is called
    # Added for external programs
    all_cleared = _pyqtSignal(int)  
    
    def __init__(self, limit_to=None, parent=None, show=True):
        self.__version__ = sciplot.__version__
        self.list_ids = []
        self.list_all = []

        # There are a number of changes and deprectaion
        # in MPL v2 and v3 ; thus, this will be tracked
        # so MPL 1 and 2 can be used seemlessly
        self._mpl_v1 = int(_mpl.__version__.rsplit('.')[0]) == 1

        # Check to see if QApp already exists
        # if not, one has to be created
        self.app = None
        if _QApplication.instance() is None:
            print('\nNo QApplication instance (this is common with certain \
            version of Matplotlib). Creating one.\n\r\
            You will need to exec manually after you finish plotting.\n\
            -----------Example---------------\n\
            import sciplot\n\
            sp = sciplot.main()\n\n\
            # Plot a line\n\
            sp.plot((0,1),(0,1))\n\n\
            # Start the QApplication\n\
            sp.app.exec_()')
            self.app = _QApplication(_sys.argv)
            self.app.setQuitOnLastWindowClosed(True)

        self.setup(limit_to=limit_to, parent=parent)
        if show:
            self.show()
       
    def closeEvent(self, event):
        pass

    def _tabAvailability(self, limit_to=None):
        """
        If limit_to is provided, limits the tabs (elements) that are available.

        May be useful for built-upon applications.
        """
        if limit_to is None:
            self.elements = ['lines', 'fill betweens', 'images', 'bars']
            self._to_setup = [self.setupLines, self.setupFillBetweens,
                              self.setupImages, self.setupBars]
        else:
            self._to_setup = []
            self.elements = []
            if limit_to.count('lines'):
                self.elements.append('lines')
                self._to_setup.append(self.setupLines)
            if limit_to.count('fill betweens'):
                self.elements.append('fill betweens')
                self._to_setup.append(self.setupFillBetweens)
            if limit_to.count('images'):
                self.elements.append('images')
                self._to_setup.append(self.setupImages)
            if limit_to.count('bars'):
                self.elements.append('bars')
                self._to_setup.append(self.setupBars)

    def setupLines(self):
        """
        Enable and setup line plotting
        """

        # Enable line plotting
        self.plot = self.__plot
        self.updatePlotDataStyle = self.__updatePlotDataStyle
        self.updatePlotDataDelete = self.__updatePlotDataDelete

        # Initial  and insert table view for line plots
        self.tableViewLine = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewLine, 'Lines')

        # Set model and delegates
        # Lines
        self.modelLine = _TableModelLines()
        self.delegateLine = _EditDelegateLines()
        self.tableViewLine.setModel(self.modelLine)
        self.tableViewLine.setItemDelegate(self.delegateLine)
        self.tableViewLine.show()


        # RESIZE COLUMNS
        header = self.tableViewLine.horizontalHeader()
        # alpha
        col = self.modelLine._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # linewidth
        col = self.modelLine._COL_LINEWIDTH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # markersize
        col = self.modelLine._COL_MARKERSIZE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # delete
        col = self.modelLine._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewLine.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewLine.doubleClicked.connect(
            self.modelLine.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelLine.dataChanged.connect(self.updatePlotDataStyle)
        self.modelLine.dataDeleted.connect(self.updatePlotDataDelete)

        # Export lines to csv
        self.ui.actionExport_Lines_to_CSV.setVisible(True)
        self.ui.actionExport_Lines_to_CSV.triggered.connect(self.export_lines_csv)

    def setupFillBetweens(self):
        """
        Enable and setup fill between plotting
        """

        # Enable fill_between plotting
        self.fill_between = self.__fill_between
        self.updateFillBetweenDataStyle = self.__updateFillBetweenDataStyle
        self.updateFillBetweenDataDelete = self.__updateFillBetweenDataDelete

        # Initial and insert table view for fill_between plots
        self.tableViewFillBetween = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewFillBetween,
                                      'Fill Between')

        # Fill Between
        self.modelFillBetween = _TableModelFillBetween()
        self.delegateFillBetween = _EditDelegateFillBetween()
        self.tableViewFillBetween.setModel(self.modelFillBetween)
        self.tableViewFillBetween.setItemDelegate(self.delegateFillBetween)
        self.tableViewFillBetween.show()

        # RESIZE COLUMNS
        header = self.tableViewFillBetween.horizontalHeader()
        # alpha
        col = self.modelFillBetween._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewFillBetween.setColumnWidth(col, new_width)

        # linewidth
        col = self.modelFillBetween._COL_LINEWIDTH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewFillBetween.setColumnWidth(col, new_width)

        # delete
        col = self.modelFillBetween._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewFillBetween.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewFillBetween.doubleClicked.connect(
            self.modelFillBetween.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelFillBetween.dataChanged.connect(self.updateFillBetweenDataStyle)
        self.modelFillBetween.dataDeleted.connect(self.updateFillBetweenDataDelete)

        # Export fillbetweens to csv
        self.ui.actionExport_Fill_Between_to_CSV.setVisible(True)
        self.ui.actionExport_Fill_Between_to_CSV.triggered.connect(self.export_fillbetweens_csv)

    def setupImages(self):
        """
        Enable and setup image plotting
        """

        # Enable imaging
        self.imshow = self.__imshow
        self.updateImagesDataStyle = self.__updateImagesDataStyle
        self.updateImagesDataDelete = self.__updateImagesDataDelete

        # images data-- similar to plot_data above

        # Initial  and insert table view for images
        self.tableViewImages = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewImages, 'Images')

        # Images
        self.modelImages = _TableModelImages()
        self.delegateImages = _EditDelegateImages()
        self.tableViewImages.setModel(self.modelImages)
        self.tableViewImages.setItemDelegate(self.delegateImages)
        self.tableViewImages.show()

        # RESIZE COLUMNS
        header = self.tableViewImages.horizontalHeader()
        # alpha
        col = self.modelImages._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # clim low
        col = self.modelImages._COL_CLIM_LOW
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # clim high
        col = self.modelImages._COL_CLIM_HIGH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # delete
        col = self.modelImages._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewImages.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewImages.doubleClicked.connect(
            self.modelImages.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelImages.dataChanged.connect(self.updateImagesDataStyle)
        self.modelImages.dataDeleted.connect(self.updateImagesDataDelete)

    def setupBars(self):
        """
        Enable and setup bar and histogram plotting
        """

        # Enable bar plotting
        self.bar = self.__bar
        self.hist = self.__hist
        self.updateBarsDataStyle = self.__updateBarsDataStyle
        self.updateBarsDataDelete = self.__updateBarsDataDelete

        # Initial  and insert table view for bars
        self.tableViewBars = _QTableView()
        self.ui.modelTabWidget.addTab(self.tableViewBars, 'Bars')


        # Bars/Bars
        self.modelBars = _TableModelBars()
        self.delegateBars = _EditDelegateBars()
        self.tableViewBars.setModel(self.modelBars)
        self.tableViewBars.setItemDelegate(self.delegateBars)
        self.tableViewBars.show()

        # RESIZE COLUMNS
        header = self.tableViewBars.horizontalHeader()
        # alpha
        col = self.modelBars._COL_ALPHA
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # linewidth
        col = self.modelBars._COL_LINEWIDTH
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # widthfactor
        col = self.modelBars._COL_WIDTH_FACTOR
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # delete
        col = self.modelBars._COL_DELETE
        new_width = int(1.1*header.sectionSizeHint(col))
        self.tableViewBars.setColumnWidth(col, new_width)

        # SIGNALS AND SLOTS

        # Make use of double-clicking within table
        self.tableViewBars.doubleClicked.connect(
            self.modelBars.doubleClickCheck)

        # When a model (table) elements changes or is deleted
        self.modelBars.dataChanged.connect(self.updateBarsDataStyle)
        self.modelBars.dataDeleted.connect(self.updateBarsDataDelete)

        # Export bars to csv
        self.ui.actionExport_Bars_to_CSV.setVisible(True)
        self.ui.actionExport_Bars_to_CSV.triggered.connect(self.export_bars_csv)

    def setup(self, limit_to=None, parent=None):
        """
        Basic UI setup
        """

        # Generic start to any pyQT program
        
        
        super(SciPlotUI, self).__init__(parent)
        self.ui = Ui_Plotter()
        self.ui.setupUi(self)
        self.setSizePolicy(_QSizePolicy.Expanding,
                           _QSizePolicy.Expanding)

        # Global "data" i.e., title, x-label, y-label, etc
        self._global_data = _DataGlobal()

        # MPL plot widget
        self.mpl_widget = _MplCanvas(height=6, dpi=100)

        # Hold is deprecated in MPL2
        if self._mpl_v1:
            self.mpl_widget.ax.hold(True)

        # Insert MPL widget and toolbar
        self.ui.verticalLayout.insertWidget(0, self.mpl_widget, 0, _QtCore.Qt.AlignHCenter)
        
        self.ui.verticalLayout.insertWidget(0, self.mpl_widget.toolbar,0, _QtCore.Qt.AlignHCenter)
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

        # Insert TabWidget
        self.ui.modelTabWidget = _QTabWidget()
        self.ui.verticalLayout.insertWidget(-1, self.ui.modelTabWidget)

        # Setup what tabs are available:
        self._tabAvailability(limit_to)
        for count in self._to_setup:
            count()

        # SIGNALS AND SLOTS
        
        # Global labels
        self.ui.lineEditTitle.editingFinished.connect(self.updateLabelsFromLineEdit)
        self.ui.lineEditXLabel.editingFinished.connect(self.updateLabelsFromLineEdit)
        self.ui.lineEditYLabel.editingFinished.connect(self.updateLabelsFromLineEdit)

        # Non-tracked (not saved) properties
        self.ui.comboBoxAspect.currentIndexChanged.connect(self.axisAspect)
        self.ui.comboBoxAxisScaling.currentIndexChanged.connect(self.axisScaling)
        self.ui.checkBoxAxisVisible.stateChanged.connect(self.axisVisible)
        self.ui.lineEditXLimMin.editingFinished.connect(self.axisLimits)
        self.ui.lineEditXLimMax.editingFinished.connect(self.axisLimits)
        self.ui.lineEditYLimMin.editingFinished.connect(self.axisLimits)
        self.ui.lineEditYLimMax.editingFinished.connect(self.axisLimits)
        
        # Actions
        self.ui.pushButtonClearAll.pressed.connect(self.clearAll)
        self.ui.pushButtonDefaultView.pressed.connect(self.defaultView)

        # Formatting Features
        self.ui.actionFigureDPI.triggered.connect(self.figureDPI)
        self.ui.actionFigureSavedDPI.triggered.connect(self.figureSaveDPI)
        self.ui.actionFigure_Size_Display.triggered.connect(self.figureSizeDisplay)
        self.ui.pushButtonApplyFigParams.pressed.connect(self.applyFigProps)

    def __plot(self, x, y, label=None, x_label=None, y_label=None, meta={}, 
               **kwargs):
        """
        MPL-like plotting functionality

        Parameters
        ----------
        x : ndarray (1D)
            X-axis data

        y : ndarray (1D, for now)
            Y-axis data

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-plot

        """

        # Temporary plot-data
        plot_data = _DataLine()
        plot_data.x = x
        plot_data.y = y
        plot_data.label = label
        plot_data.meta = meta
        plot_data.id = _time.time()

        # Plot outputs a line object
        plot_data.mplobj = self.mpl_widget.ax.plot(x, y, label=label, **kwargs)
        try:
            self.mpl_widget.ax.legend(loc='best')
        except:
            pass

        # If labels are provided, update the global data and the linEdits
        if x_label is not None or y_label is not None:
            self.updateAllLabels(x_label=x_label, y_label=y_label)

        self.mpl_widget.fig.tight_layout()
        self.axisAspect()
        self.mpl_widget.draw()


        # Since the plot was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup color, linewidth, etc.
        # Thus, we plot, then retrieve what the style info was
        plot_data.retrieve_style_from_line(plot_data.mplobj[0])

        # Append this specific plot data to out list of all plots
        self.list_ids.append(plot_data.id)
        self.list_all.append(plot_data)

        # Update model
        self.modelLine._model_data.append(plot_data.model_style)
        self.modelLine.layoutChanged.emit()

    def updateMplLabels(self, x_label=None, y_label=None, title=None):
        """
        Within the MPL widget, update the x- and y-labels and the title
        """
        if x_label is not None:
            self.mpl_widget.ax.set_xlabel(x_label)

        if y_label is not None:
            self.mpl_widget.ax.set_ylabel(y_label)

        if title is not None:
            self.mpl_widget.ax.set_title(title)
        self.mpl_widget.fig.tight_layout()
        self.mpl_widget.draw()

    def updateDataLabels(self, x_label=None, y_label=None, title=None):
        """
        Within the global data container, update the x- and y-labels and the \
        title
        """
        if x_label is not None:
            self._global_data.labels['x_label'] = x_label

        if y_label is not None:
            self._global_data.labels['y_label'] = y_label

        if title is not None:
            self._global_data.labels['title'] = title

    def updateLineEditLabels(self, x_label=None, y_label=None, title=None):
        """
        Within the pyQT lineEdit widgets, update the x- and y-labels and the \
        title
        """
        if x_label is not None:
            self.ui.lineEditXLabel.setText(x_label)

        if y_label is not None:
            self.ui.lineEditYLabel.setText(y_label)

        if title is not None:
            self.ui.lineEditTitle.setText(title)

    def updateAllLabels(self, x_label=None, y_label=None, title=None):
        """
        Update the x- and y-labels and the title in the MPL widget, the \
        lineEdit boxes, and the global data container
        """
        self.updateMplLabels(x_label=x_label, y_label=y_label, title=title)
        self.updateDataLabels(x_label=x_label, y_label=y_label, title=title)
        self.updateLineEditLabels(x_label=x_label, y_label=y_label,
                                  title=title)

    def updateLabelsFromLineEdit(self):
        """
        From the linEdit widgets, update the x- and y-labels and the title \
        in the MPL widget and the global data container
        """
        title = None
        x_label = None
        y_label = None

        sender = self.sender()
        if sender == self.ui.lineEditTitle:
            title = self.ui.lineEditTitle.text()
        elif sender == self.ui.lineEditXLabel:
            x_label = self.ui.lineEditXLabel.text()
        elif sender == self.ui.lineEditYLabel:
            y_label = self.ui.lineEditYLabel.text()
        self.updateDataLabels(x_label=x_label, y_label=y_label, title=title)
        self.updateMplLabels(x_label=x_label, y_label=y_label, title=title)

    def __updatePlotDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the plot data
        """
        for num, style_info in enumerate(self.modelLine._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updatePlotDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        try:
            idx_to_remove = self.list_ids.index(plt_id)
            self.list_ids.pop(idx_to_remove)
            self.list_all.pop(idx_to_remove)
        except:
            print('Error in __updatePlotDataDelete: {}'.format(idx_to_remove))
            
        self.refreshAllPlots()

    def refreshAllPlots(self):
        """
        Clear and re-plot all plot data of all types
        """

        # Clear axis -- in the future, maybe clear figure and recreate axis
        self.mpl_widget.ax.clear()
        
        for itm in self.list_all:
            if isinstance(itm, _DataLine):
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                itm.mplobj = self.mpl_widget.ax.plot(itm.x, itm.y, 
                                                     label=label,
                                                     color=itm.style_dict['color'],
                                                     alpha=itm.style_dict['alpha'],
                                                     linewidth=itm.style_dict['linewidth'],
                                                     linestyle=itm.style_dict['linestyle'],
                                                     marker=itm.style_dict['marker'],
                                                     markersize=itm.style_dict['markersize'])
            elif isinstance(itm, _DataBar):
#                print('Bar')
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                itm.mplobj = self.mpl_widget.ax.bar(itm._left, itm.y, 
                                                    bottom=itm.bottom,
                                                    width=itm._width,
                                                    label=label,
                                                    facecolor=itm.style_dict['facecolor'],
                                                    alpha=itm.style_dict['alpha'],
                                                    edgecolor=itm.style_dict['edgecolor'],
                                                    linewidth=itm.style_dict['linewidth'])
            elif isinstance(itm, _DataImages):
#                print('Images')
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                if itm.cbar['obj'] is not None:
                    try:  # Have had some unknown exceptions with .remove()
                        itm.cbar['obj'].remove()
                        itm.cbar['obj'] = None
                    except:
                        pass
                itm.mplobj = self.mpl_widget.ax.imshow(itm.img, label=label,
                                                       interpolation='none',
                                                       origin='lower',
                                                       cmap=_mpl.cm.cmap_d[itm.style_dict['cmap_name']],
                                                       alpha=itm.style_dict['alpha'],
                                                       clim=itm.style_dict['clim'])
                if itm.cbar['show']:
                    itm.cbar['obj'] = self.mpl_widget.fig.colorbar(itm.mplobj,
                                                                   use_gridspec=True)
            elif isinstance(itm, _DataFillBetween):
#                print('Fill Between')
                if self._mpl_v1:
                    self.mpl_widget.ax.hold(True)
                
                # Hide label if alpha=0
                if itm.style_dict['alpha'] == 0:
                    label = None
                else:
                    label = itm.label
                itm.mplobj = self.mpl_widget.ax.fill_between(itm.x, itm.y_low, itm.y_high,
                                                  label=label,
                                                  facecolor=itm.style_dict['facecolor'],
                                                  edgecolor=itm.style_dict['edgecolor'],
                                                  alpha=itm.style_dict['alpha'],
                                                  linewidth=itm.style_dict['linewidth'])

            else:
                print('Unknown')

        # Only add legend if legend handles exist
        h,l = self.mpl_widget.ax.get_legend_handles_labels()
        if l:
            self.mpl_widget.ax.legend(loc='best')

        # Apply x- and y-labels and a title if they are set
        if self._global_data.labels['title'] is not None:
            self.mpl_widget.ax.set_title(self._global_data.labels['title'])
        if self._global_data.labels['x_label'] is not None:
            self.mpl_widget.ax.set_xlabel(self._global_data.labels['x_label'])
        if self._global_data.labels['y_label'] is not None:
            self.mpl_widget.ax.set_ylabel(self._global_data.labels['y_label'])

        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.axisAspect()
        self.mpl_widget.draw()

    def __fill_between(self, x, y_low, y_high, label=None,  meta={},
                       x_label=None, y_label=None, **kwargs):
        """
        MPL-like fill_between plotting functionality

        Parameters
        ----------
        x : ndarray (1D)
            X-axis data

        y_low : ndarray (1D, for now)
            Low Y-axis data

        y_high : ndarray (1D, for now)
            High Y-axis data

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-fill_between

        """

        # Temporary fill_between-data
        fill_between_data = _DataFillBetween()
        fill_between_data.x = x
        fill_between_data.y_low = y_low
        fill_between_data.y_high = y_high
        fill_between_data.label = label
        fill_between_data.meta = meta
        fill_between_data.id = _time.time()

        # Fill between outputs a polycollection
        fill_between_data.mplobj = self.mpl_widget.ax.fill_between(x, y_low, y_high,
                                                   label=label, **kwargs)
        self.mpl_widget.ax.legend(loc='best')
        self.mpl_widget.fig.tight_layout()
        self.axisAspect()
        self.mpl_widget.draw()

        # Since the fill_between was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup color, linewidth, etc.
        # Thus, we plot, then retrieve what the style info was
        fill_between_data.retrieve_style_from_polycollection(fill_between_data.mplobj)

        # Append this specific plot data to out list of all plots
        self.list_ids.append(fill_between_data.id)
        self.list_all.append(fill_between_data)

        # Update model
        self.modelFillBetween._model_data.append(fill_between_data.model_style)
        self.modelFillBetween.layoutChanged.emit()
        

    def __updateFillBetweenDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the fill_between data
        """
        for num, style_info in enumerate(self.modelFillBetween._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updateFillBetweenDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        idx_to_remove = self.list_ids.index(plt_id)
        self.list_ids.pop(idx_to_remove)
        self.list_all.pop(idx_to_remove)
        
        self.refreshAllPlots()

    def __imshow(self, img, x=None, y=None, label=None, meta={},
               x_label=None, y_label=None, cbar=False, **kwargs):
        """
        MPL-like plotting functionality

        Parameters
        ----------
        img : ndarray (2D)
            Image data

        x : ndarray (1D)
            X-axis data

        y : ndarray (1D, for now)
            Y-axis data

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)
            
        cbar : bool
            Attach a colorbar to the img

        kwargs : dict
            Other parameters sent directly to mpl-imshow

        """

        # Temporary plot-data
        image_data = _DataImages()
        image_data.img = img
        image_data.x = x
        image_data.y = y
        image_data.label = label
        image_data.meta = meta
        image_data.id = _time.time()
        image_data.cbar['show'] = cbar

        # Imshow outputs an image object
        image_data.mplobj = self.mpl_widget.ax.imshow(img, interpolation='None',
                                                origin='lower',
                                                label=label,
                                                **kwargs)
        if image_data.cbar['show']:
            image_data.cbar['obj'] = self.mpl_widget.fig.colorbar(image_data.mplobj,
                                                use_gridspec=True)
            
#        self.mpl_widget.ax.legend(loc='best')

        # If labels are provided, update the global data and the linEdits
        if x_label is not None or y_label is not None:
            self.updateAllLabels(x_label=x_label, y_label=y_label)

        self.mpl_widget.fig.tight_layout()
        
        self.axisAspect()
        self.mpl_widget.draw()

        # Since the image was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup cmap, etc.
        # Thus, we plot, then retrieve what the style info was
        image_data.retrieve_style_from_image(image_data.mplobj)

        # Append this specific plot data to out list of all plots
        self.list_ids.append(image_data.id)
        self.list_all.append(image_data)

        # Update model
        self.modelImages._model_data.append(image_data.model_style)
        self.modelImages.layoutChanged.emit()


    def __updateImagesDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the fill_between data
        """
        for num, style_info in enumerate(self.modelImages._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updateImagesDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        
        idx_to_remove = self.list_ids.index(plt_id)
        self.list_ids.pop(idx_to_remove)
        popd = self.list_all.pop(idx_to_remove)
            
        if popd.cbar['obj'] is not None:
            popd.cbar['obj'].remove()
#        self.axisAspect()
        self.refreshAllPlots()


    def __bar(self, x, y, bottom=0, width_factor=1.0, use_real_width=False, 
              label=None, meta={}, x_label=None, y_label=None, **kwargs):
        """
        MPL-like plotting functionality

        Note
        ----
        Unlike MPL bar, this method uses centered data. Thus, x is the center \
        position of the bar

        Parameters
        ----------
        x : ndarray (1D)
            X-axis data (center of bars)

        y : ndarray (1D, for now)
            Y-axis data (height)

        bottom : float (for now)
            Baseline of bars
            
        width_factor: float
            If legnth of y>1, fraction of space between bars taken up by bar \
            (e.g. 1.0 leads to bars that tough). If y is a single-value OR \
            use_real_width is True), is the width of the bar.

        use_real_width : bool, optional (default=False):
            If True, width_factor is the real width (in x-units)
            
        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-plot

        """

        # Temporary plot-data
        bar_data = _DataBar()
        bar_data.x = x
        bar_data.y = y
        bar_data.bottom = bottom
        bar_data.label = label
        bar_data.meta = meta
        bar_data.id = _time.time()

        bar_data.style_dict['width_factor'] = width_factor

        _multi_value = None

        if isinstance(y, (int, float)):
            _multi_value = False
        if isinstance(y, _np.ndarray):
            if y.size == 1:
                _multi_value = False
            else:
                _multi_value = True
        if isinstance(y, (list, tuple)):
            if len(y) == 1:
                _multi_value = False
            else:
                _multi_value = True

        if _multi_value and use_real_width == False:
            # Distance between bars
            bar_data._gap = _np.abs(x[1]-x[0])

            # Width of a bar is a fraction of the gap
            bar_data._width = bar_data._gap*bar_data.style_dict['width_factor']

        else:
            # Single-valued: no gap
            bar_data._gap = None
            bar_data._width = width_factor

        # MPL-bar uses left-edge rather than center
        bar_data._left = bar_data.x - bar_data._width/2

        # Plot outputs a list of patch objects
        bar_data.mplobj = self.mpl_widget.ax.bar(bar_data._left, y,
                                           bottom=bar_data.bottom,
                                           width=bar_data._width,
                                           label=label, **kwargs)
        self.mpl_widget.ax.legend(loc='best')

        # If labels are provided, update the global data and the linEdits
        if x_label is not None or y_label is not None:
            self.updateAllLabels(x_label=x_label, y_label=y_label)

        self.mpl_widget.fig.tight_layout()
        
        self.axisAspect()
        self.mpl_widget.draw()


        # Since the plot was not fed style-info (unless kwargs were used)
        # we rely on the mpl stylesheet to setup color, linewidth, etc.
        # Thus, we plot, then retrieve what the style info was
        bar_data.retrieve_style_from_bar(bar_data.mplobj[0])
    
        # Append this specific plot data to out list of all plots
        self.list_ids.append(bar_data.id)
        self.list_all.append(bar_data)
        
        # Update model
        self.modelBars._model_data.append(bar_data.model_style)
        self.modelBars.layoutChanged.emit()
        
        # Note: New in MPL2, edgecolor is RGBA with A defaulting to 0
        # (ie transparent, which Sciplot does not currently support).
        self.refreshAllPlots()

    def __hist(self, data, bins=10, label=None, meta={}, x_label=None,
             y_label='Counts', **kwargs):
        """
        MPL-like histogram plotting

        Parameters
        ----------
        data : ndarray (1D, for now)
            Data (center of bars)

        bins : int
            Number of histogram bins

        label : str
            Label of plot

        x_label : str
            X-axis label (units)

        y_label : str
            Y-axis label (units)

        kwargs : dict
            Other parameters sent directly to mpl-plot

        """
        counts, lefts = _np.histogram(data, bins=bins)
        gap = _np.abs(lefts[1] - lefts[0])
        offset = gap/2

        self.bar(lefts[:-1]+offset, counts, width_factor=1.0, label=label,
                 x_label=x_label, y_label=y_label, meta=meta, **kwargs)

    def __updateBarsDataStyle(self):
        """
        Something style-related changed in the model; thus, need to change \
        these elements in the fill_between data
        """
        for num, style_info in enumerate(self.modelBars._model_data):
            idx = self.list_ids.index(style_info['id'])
            self.list_all[idx].model_style = style_info
        self.refreshAllPlots()

    def __updateBarsDataDelete(self, row, plt_id):
        """
        A plot was deleted (likely from within the model); thus, need to \
        remove the corresponding plot data
        """
        idx_to_remove = self.list_ids.index(plt_id)
        self.list_ids.pop(idx_to_remove)
        self.list_all.pop(idx_to_remove)
        
        self.refreshAllPlots()

    def axisAspect(self):
        """
        Set axis aspect ratio property
        """
        aspect = self.ui.comboBoxAspect.currentText()
        self.mpl_widget.ax.set_aspect(aspect)
        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def axisScaling(self):
        """
        Set axis scaling property
        """
        ratio = self.ui.comboBoxAxisScaling.currentText()
        self.mpl_widget.ax.axis(ratio)
        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def axisVisible(self):
        """
        Set whether axis is on or off
        """
        state = self.ui.checkBoxAxisVisible.isChecked()
        if state:
            state = 'on'
        else:
            state = 'off'

        self.mpl_widget.ax.axis(state)
        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def axisLimits(self):
        """
        Set axis limits
        """
        if self.sender() == self.ui.lineEditXLimMin:
            value = float(self.ui.lineEditXLimMin.text())
            self.mpl_widget.ax.axis(xmin=value)
        elif self.sender() == self.ui.lineEditXLimMax:
            value = float(self.ui.lineEditXLimMax.text())
            self.mpl_widget.ax.axis(xmax=value)
        elif self.sender() == self.ui.lineEditYLimMin:
            value = float(self.ui.lineEditYLimMin.text())
            self.mpl_widget.ax.axis(ymin=value)
        elif self.sender() == self.ui.lineEditYLimMax:
            value = float(self.ui.lineEditYLimMax.text())
            self.mpl_widget.ax.axis(ymax=value)

        self.mpl_widget.fig.tight_layout()
        self.updateAxisParameters()
        self.updateFigureParameters()
        self.mpl_widget.draw()

    def updateAxisParameters(self):
        """
        Query current state of axis settings and update appropriate lineEdit's
        """
        axis_visible = self.mpl_widget.ax.axison
        self.ui.checkBoxAxisVisible.setChecked(axis_visible)
        xmin, xmax, ymin, ymax = self.mpl_widget.ax.axis()
        self.ui.lineEditXLimMin.setText(str(round(xmin,5)))
        self.ui.lineEditXLimMax.setText(str(round(xmax,5)))
        self.ui.lineEditYLimMin.setText(str(round(ymin,5)))
        self.ui.lineEditYLimMax.setText(str(round(ymax,5)))

    def updateFigureParameters(self):
        """
        Query current state of axis settings and update appropriate lineEdit's
        """
        fig_dpi = self.mpl_widget.fig.get_dpi()
        save_dpi = _mpl.rcParams['savefig.dpi']
        if save_dpi is 'figure':
            save_dpi = fig_dpi
        fig_size = self.mpl_widget.fig.get_size_inches()
        
        self.ui.spinBoxFigureDPI.setValue(fig_dpi)
        self.ui.spinBoxFigureSavedDPI.setValue(save_dpi)
        self.ui.spinBoxFigSizeWidth.setValue(fig_size[0])
        self.ui.spinBoxFigSizeHeight.setValue(fig_size[1])
        
    def defaultView(self):
        """
        Set default and Home view to the current one
        """
        # New versions of MPL don't have these functions
        try:
            self.mpl_widget.toolbar._views.clear()
            self.mpl_widget.toolbar._positions.clear()
        except:
            pass

        self.mpl_widget.toolbar.update()
    
    def clearAllBars(self):
        try:
            self.modelBars._model_data = []
            ids = self.list_bar_ids
            for i in ids:
                self.clearID(i)
            
            self.modelBars.layoutChanged.emit()
        except:
            print('Error in clearAllBars')

    def clearID(self, clear_id):
        idx_to_remove = self.list_ids.index(clear_id)
        self.list_ids.pop(idx_to_remove)
        self.list_all.pop(idx_to_remove)

    def clearAll(self):
        """
        Clear all plots and graphs and images
        """
        try:
            self.modelLine._model_data = []
            self.modelLine.layoutChanged.emit()
        except:
            print('Error in clear all of plots/lines')
        
        try:
            self.modelBars._model_data = []
            self.modelBars.layoutChanged.emit()
        except:
            print('Error in clear all of bars')
        
        
        try:
            # Need to iterate as to check for colorbar existance
            for num, model_data in enumerate(self.modelImages._model_data):
                idx_to_remove = self.list_ids.index(model_data['id'])
                self.list_ids.pop(idx_to_remove)
                popd = self.list_all.pop(idx_to_remove)
                if popd.cbar['obj'] is not None:
                    popd.cbar['obj'].remove()
            
            self.modelImages._model_data = []
            self.modelImages.layoutChanged.emit()
        except:
            print('Error in clear all of images')
        
        try:
            self.modelFillBetween._model_data = []
            self.modelFillBetween.layoutChanged.emit()
        except:
            print('Error in clear all of fill-betweens')
            
        try:
            self.list_ids = []
            self.list_all = []
        except:
            print('Error in clear all')
        finally:
            self.refreshAllPlots()
            self.all_cleared.emit(id(self))

    def export_bars_csv(self):
        ret = _QFileDialog.getSaveFileName(filter="Comma-Separated Values (*.csv);;All Files (*.*)")
        if ret[0]:
            # pth, fname = _os.path.split(ret[0])
            with open(ret[0],'w') as f:
                for q in self.list_bar_objs:
                    f.write('{}\n'.format(q.label))
                    f.write('left,')
                    q._left.tofile(f, sep=',')
                    f.write('\nx,')
                    q.x.tofile(f, sep=',')
                    f.write('\ny,')
                    q.y.tofile(f,sep=',')
                    f.write('\n\n')    

    def export_lines_csv(self):
        ret = _QFileDialog.getSaveFileName(filter="Comma-Separated Values (*.csv);;All Files (*.*)")
        if ret[0]:
            # pth, fname = _os.path.split(ret[0])
            with open(ret[0],'w') as f:
                for q in self.list_line_objs:
                    f.write('{}\n'.format(q.label))
                    f.write('x,')
                    q.x.tofile(f, sep=',')
                    f.write('\ny,')
                    q.y.tofile(f,sep=',')
                    f.write('\n\n')

    def export_fillbetweens_csv(self):
        ret = _QFileDialog.getSaveFileName(filter="Comma-Separated Values (*.csv);;All Files (*.*)")
        if ret[0]:
            # pth, fname = _os.path.split(ret[0])
            with open(ret[0],'w') as f:
                for q in self.list_fillbetween_objs:
                    f.write('{}\n'.format(q.label))
                    f.write('x,')
                    q.x.tofile(f, sep=',')
                    f.write('\ny_low,')
                    q.y_low.tofile(f,sep=',')
                    f.write('\ny_high,')
                    q.y_high.tofile(f,sep=',')
                    f.write('\n\n')

    @property
    def n_lines(self):
        return sum(isinstance(x, _DataLine) for x in self.list_all)
    
    @property
    def n_bars(self):
        return sum(isinstance(x, _DataBar) for x in self.list_all)
        
    @property
    def n_fillbetweens(self):
        return sum(isinstance(x, _DataFillBetween) for x in self.list_all)
        
    @property
    def n_images(self):
        return sum(isinstance(x, _DataImages) for x in self.list_all)
        
    @property
    def list_line_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataLine)]
        
    @property
    def list_line_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataLine)]
        
    @property
    def list_bar_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataBar)]
        
    @property
    def list_bar_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataBar)]
        
    @property
    def list_fillbetween_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataFillBetween)]
        
    @property
    def list_fillbetween_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataFillBetween)]
        
    @property
    def list_image_objs(self):
        return [x for x in self.list_all if isinstance(x, _DataImages)]
        
    @property
    def list_image_ids(self):
        return [x.id for x in self.list_all if isinstance(x, _DataImages)]
        
    def figureDPI(self):
        curr_dpi = self.mpl_widget.fig.get_dpi()
        dpi, okPressed = _QInputDialog.getInt(self, "New Figure DPI","DPI:", curr_dpi, 10, 100000, 25)
        if okPressed:
            self.mpl_widget.fig.set_dpi(dpi)
            self.mpl_widget.updateGeometry()
            self.mpl_widget.draw()
        self.updateFigureParameters()

    def figureSaveDPI(self):
        curr_dpi = _mpl.rcParams['savefig.dpi']
        if not isinstance(curr_dpi, int):
            # Savefig is set to 'figure'
            curr_dpi = self.mpl_widget.fig.get_dpi()
        
        dpi, okPressed = _QInputDialog.getInt(self, "New DPI for Saved Figures","DPI:", curr_dpi, 10, 100000, 25)
        if okPressed:
            _mpl.rcParams['savefig.dpi'] = dpi
        self.updateFigureParameters()

    def figureSizeDisplay(self):
        curr_size = self.mpl_widget.fig.get_size_inches()
        new_size, okPressed = DualEntry.getDualEntries(curr_size[0], curr_size[1], input_type=float, text="Figure Size (W x H inches)", parent=self)
        if okPressed:
            self.mpl_widget.updateGeometry()
            self.mpl_widget.fig.set_size_inches(new_size[0], new_size[1], forward=True)
            self.mpl_widget.draw()
        self.updateFigureParameters()

    def applyFigProps(self):
        """ Apply manually-entered figure properties (e.g., dpi) """
        
        new_fig_size = (self.ui.spinBoxFigSizeWidth.value(), self.ui.spinBoxFigSizeHeight.value())
        new_fig_dpi = self.ui.spinBoxFigureDPI.value()
        new_save_dpi = self.ui.spinBoxFigureSavedDPI.value()

        self.mpl_widget.fig.set_dpi(new_fig_dpi)
        self.mpl_widget.fig.set_size_inches(new_fig_size)
        _mpl.rcParams['savefig.dpi'] = new_save_dpi
        self.mpl_widget.updateGeometry()
        self.mpl_widget.draw()

if __name__ == '__main__':

    app = _QApplication(_sys.argv)
    #app.setQuitOnLastWindowClosed(True)
    
    winPlotter = SciPlotUI(limit_to=['lines','bars', 'fill betweens',
                                     'images'])
    #winPlotter.show()

    x = _np.arange(100)
    y = x**2

    winPlotter.plot(x, y, x_label='X', label='Plot')
#    winPlotter.plot(x, y**1.1, label='Plot 2')
    winPlotter.fill_between(x, y-1000, y+1000, label='Fill Between')
#
    winPlotter.imshow(_np.random.randn(100,100), label='Imshow', cbar=True)
    winPlotter.bar(x[::10],y[::10],label='Bar')
#    winPlotter.hist(y,label='Hist')

#    winPlotter.bar(0,10, label='Bar: single-value')
    app.exec_()
# -*- coding: utf-8 -*- SciPlot-PyQt: Publication-ready scientific plotting for Python ==============================================================  SciPlot-PyQt (aka SciPlot) is a user-interface/matplotlib wrapper built with  PyQt5 that allows interactive plotting through an embedded matplotlib canvas.  It enables fast and easy publication-ready plots and images:          * Interactive plotting     * Theme and style editing (TODO)     * Figure saving and opening for later editing (TODO)      Supported Plot Types --------------------- Line plots : plot  Bar plots : bar, hist  Polycollections : fill_between  Images : imshow  Notes ----- SciPlot has a lot of advances/improvements to make. Feel free to contact me-- help is always welcome!  Usage ----- import sciplot sp = sciplot.SciPlotUI() sp.show()  Example ------- sp.plot((0,1),(2,3),label='Line', x_label='X', y_label='Y', ls='--') sp.fill_between((0,1),(1,2),(3,4),label='Fill Between', color='r', alpha=0.25)  Authors ------- * <NAME>. <<EMAIL>> # Generic imports for MPL-incorporation # Simply for it starting a QApplication # Generic imports for QT-based programs # Import from Designer-based GUI Scientific plotting user-interface for creating publication-quality plots     and images      Parameters     ----------      limit_to : list, optional (default = None)         Limit the application to implement only certain functionality. \         Default is all elements turned ON. See Notes for options.              show : bool, optional (default = True)         Whether to show the UI upon instantiation      Methods     -------     plot : MPL-like plotting functionality      imshow : MPL-like imshow      bar : MPL-like bar plot EXCEPT centered (rather than left-edge defined)      hist : MPL-like histogram      fill_between : MPL-like fill_between      Internal Methods     ----------------     updatePlotDataStyle : Make updates to plots (lines) when a stylistic \     change is made within the model-table      updatePlotDataDelete : Remove a plot when deleted from model-table      updateFillBetweenDataStyle : Make updates to fill between's when a \     stylistic change is made within the model-table      updateFillBetweenDataDelete : Remove a fill between when deleted from \     model-table      updateImagesDataStyle : Make updates to images when a stylistic \     change is made within the model-table      updateImageDataDelete : Remove an image when deleted from model-table      updateBarsDataStyle : Make updates to bars plots when a stylistic \     change is made within the model-table      updateBarsDataDelete : Remove a bar plot when deleted from model-table      refreshAllPlots : Delete all plots and re-plot      updateAllLabels : Update all labels (x-, y-, title, etc) on MPL widget, \         in model, in data container, and in UI lineEdits      updateLineEditLabels : Update all labels (x-, y-, title, etc) in UI \         lineEdits      updateDataLabels : Update all labels (x-, y-, title, etc) in data \     container      updateMplLabels : Update all labels (x-, y-, title, etc) on MPL widget      updateLabelsFromLineEdit : Update all labels (x-, y-, title, etc) on MPL \         widget, in model, and in data container. Edits came from lineEdits.      axisAspect : Set MPL-axis aspect ratio setting      axisScaling : Set MPL-axis scaling ratio setting      axisVisible : Set MPL-axis on or off      axisLimits : Set MPL-axis limits      updateAxisParameters : Query and update UI lineEdits related to axis \         properties such as limits, visibility (on/off), scaling, and aspect \         ratio      Notes     -----     * limit_to options: 'lines', 'fill betweens', 'bars', images' # Signal emitted when clearAll is called # Added for external programs # There are a number of changes and deprectaion # in MPL v2 and v3 ; thus, this will be tracked # so MPL 1 and 2 can be used seemlessly # Check to see if QApp already exists # if not, one has to be created # Plot a line\n\ # Start the QApplication\n\ If limit_to is provided, limits the tabs (elements) that are available.          May be useful for built-upon applications. Enable and setup line plotting # Enable line plotting # Initial  and insert table view for line plots # Set model and delegates # Lines # RESIZE COLUMNS # alpha # linewidth # markersize # delete # SIGNALS AND SLOTS # Make use of double-clicking within table # When a model (table) elements changes or is deleted # Export lines to csv Enable and setup fill between plotting # Enable fill_between plotting # Initial and insert table view for fill_between plots # Fill Between # RESIZE COLUMNS # alpha # linewidth # delete # SIGNALS AND SLOTS # Make use of double-clicking within table # When a model (table) elements changes or is deleted # Export fillbetweens to csv Enable and setup image plotting # Enable imaging # images data-- similar to plot_data above # Initial  and insert table view for images # Images # RESIZE COLUMNS # alpha # clim low # clim high # delete # SIGNALS AND SLOTS # Make use of double-clicking within table # When a model (table) elements changes or is deleted Enable and setup bar and histogram plotting # Enable bar plotting # Initial  and insert table view for bars # Bars/Bars # RESIZE COLUMNS # alpha # linewidth # widthfactor # delete # SIGNALS AND SLOTS # Make use of double-clicking within table # When a model (table) elements changes or is deleted # Export bars to csv Basic UI setup # Generic start to any pyQT program # Global "data" i.e., title, x-label, y-label, etc # MPL plot widget # Hold is deprecated in MPL2 # Insert MPL widget and toolbar # Insert TabWidget # Setup what tabs are available: # SIGNALS AND SLOTS # Global labels # Non-tracked (not saved) properties # Actions # Formatting Features MPL-like plotting functionality          Parameters         ----------         x : ndarray (1D)             X-axis data          y : ndarray (1D, for now)             Y-axis data          label : str             Label of plot          x_label : str             X-axis label (units)          y_label : str             Y-axis label (units)          kwargs : dict             Other parameters sent directly to mpl-plot # Temporary plot-data # Plot outputs a line object # If labels are provided, update the global data and the linEdits # Since the plot was not fed style-info (unless kwargs were used) # we rely on the mpl stylesheet to setup color, linewidth, etc. # Thus, we plot, then retrieve what the style info was # Append this specific plot data to out list of all plots # Update model Within the MPL widget, update the x- and y-labels and the title Within the global data container, update the x- and y-labels and the \         title Within the pyQT lineEdit widgets, update the x- and y-labels and the \         title Update the x- and y-labels and the title in the MPL widget, the \         lineEdit boxes, and the global data container From the linEdit widgets, update the x- and y-labels and the title \         in the MPL widget and the global data container Something style-related changed in the model; thus, need to change \         these elements in the plot data A plot was deleted (likely from within the model); thus, need to \         remove the corresponding plot data Clear and re-plot all plot data of all types # Clear axis -- in the future, maybe clear figure and recreate axis # Hide label if alpha=0 #                print('Bar') # Hide label if alpha=0 #                print('Images') # Hide label if alpha=0 # Have had some unknown exceptions with .remove() #                print('Fill Between') # Hide label if alpha=0 # Only add legend if legend handles exist # Apply x- and y-labels and a title if they are set MPL-like fill_between plotting functionality          Parameters         ----------         x : ndarray (1D)             X-axis data          y_low : ndarray (1D, for now)             Low Y-axis data          y_high : ndarray (1D, for now)             High Y-axis data          label : str             Label of plot          x_label : str             X-axis label (units)          y_label : str             Y-axis label (units)          kwargs : dict             Other parameters sent directly to mpl-fill_between # Temporary fill_between-data # Fill between outputs a polycollection # Since the fill_between was not fed style-info (unless kwargs were used) # we rely on the mpl stylesheet to setup color, linewidth, etc. # Thus, we plot, then retrieve what the style info was # Append this specific plot data to out list of all plots # Update model Something style-related changed in the model; thus, need to change \         these elements in the fill_between data A plot was deleted (likely from within the model); thus, need to \         remove the corresponding plot data MPL-like plotting functionality          Parameters         ----------         img : ndarray (2D)             Image data          x : ndarray (1D)             X-axis data          y : ndarray (1D, for now)             Y-axis data          label : str             Label of plot          x_label : str             X-axis label (units)          y_label : str             Y-axis label (units)                      cbar : bool             Attach a colorbar to the img          kwargs : dict             Other parameters sent directly to mpl-imshow # Temporary plot-data # Imshow outputs an image object #        self.mpl_widget.ax.legend(loc='best') # If labels are provided, update the global data and the linEdits # Since the image was not fed style-info (unless kwargs were used) # we rely on the mpl stylesheet to setup cmap, etc. # Thus, we plot, then retrieve what the style info was # Append this specific plot data to out list of all plots # Update model Something style-related changed in the model; thus, need to change \         these elements in the fill_between data A plot was deleted (likely from within the model); thus, need to \         remove the corresponding plot data #        self.axisAspect() MPL-like plotting functionality          Note         ----         Unlike MPL bar, this method uses centered data. Thus, x is the center \         position of the bar          Parameters         ----------         x : ndarray (1D)             X-axis data (center of bars)          y : ndarray (1D, for now)             Y-axis data (height)          bottom : float (for now)             Baseline of bars                      width_factor: float             If legnth of y>1, fraction of space between bars taken up by bar \             (e.g. 1.0 leads to bars that tough). If y is a single-value OR \             use_real_width is True), is the width of the bar.          use_real_width : bool, optional (default=False):             If True, width_factor is the real width (in x-units)                      label : str             Label of plot          x_label : str             X-axis label (units)          y_label : str             Y-axis label (units)          kwargs : dict             Other parameters sent directly to mpl-plot # Temporary plot-data # Distance between bars # Width of a bar is a fraction of the gap # Single-valued: no gap # MPL-bar uses left-edge rather than center # Plot outputs a list of patch objects # If labels are provided, update the global data and the linEdits # Since the plot was not fed style-info (unless kwargs were used) # we rely on the mpl stylesheet to setup color, linewidth, etc. # Thus, we plot, then retrieve what the style info was # Append this specific plot data to out list of all plots # Update model # Note: New in MPL2, edgecolor is RGBA with A defaulting to 0 # (ie transparent, which Sciplot does not currently support). MPL-like histogram plotting          Parameters         ----------         data : ndarray (1D, for now)             Data (center of bars)          bins : int             Number of histogram bins          label : str             Label of plot          x_label : str             X-axis label (units)          y_label : str             Y-axis label (units)          kwargs : dict             Other parameters sent directly to mpl-plot Something style-related changed in the model; thus, need to change \         these elements in the fill_between data A plot was deleted (likely from within the model); thus, need to \         remove the corresponding plot data Set axis aspect ratio property Set axis scaling property Set whether axis is on or off Set axis limits Query current state of axis settings and update appropriate lineEdit's Query current state of axis settings and update appropriate lineEdit's Set default and Home view to the current one # New versions of MPL don't have these functions Clear all plots and graphs and images # Need to iterate as to check for colorbar existance # pth, fname = _os.path.split(ret[0]) # pth, fname = _os.path.split(ret[0]) # pth, fname = _os.path.split(ret[0]) # Savefig is set to 'figure' Apply manually-entered figure properties (e.g., dpi) #app.setQuitOnLastWindowClosed(True) #winPlotter.show() #    winPlotter.plot(x, y**1.1, label='Plot 2') # #    winPlotter.hist(y,label='Hist') #    winPlotter.bar(0,10, label='Bar: single-value')