From 173e44c797aed2d3385338ec40912d686c885124 Mon Sep 17 00:00:00 2001
From: Wenduo Zhou <zhouw@ornl.gov>
Date: Fri, 9 Oct 2015 07:12:43 -0400
Subject: [PATCH] Refs #12122. Added python classes.

---
 .../HFIR_4Circle_Reduction/NTableWidget.py    |  253 ++++
 .../fourcircle_utility.py                     |  188 +++
 scripts/HFIR_4Circle_Reduction/guiutility.py  |  169 +++
 scripts/HFIR_4Circle_Reduction/hfctables.py   |  364 +++++
 .../HFIR_4Circle_Reduction/mplgraphicsview.py | 1255 +++++++++++++++++
 5 files changed, 2229 insertions(+)
 create mode 100644 scripts/HFIR_4Circle_Reduction/NTableWidget.py
 create mode 100644 scripts/HFIR_4Circle_Reduction/fourcircle_utility.py
 create mode 100644 scripts/HFIR_4Circle_Reduction/guiutility.py
 create mode 100644 scripts/HFIR_4Circle_Reduction/hfctables.py
 create mode 100644 scripts/HFIR_4Circle_Reduction/mplgraphicsview.py

diff --git a/scripts/HFIR_4Circle_Reduction/NTableWidget.py b/scripts/HFIR_4Circle_Reduction/NTableWidget.py
new file mode 100644
index 00000000000..d7a2ed43bb8
--- /dev/null
+++ b/scripts/HFIR_4Circle_Reduction/NTableWidget.py
@@ -0,0 +1,253 @@
+#pylint: disable=C0103,R0904
+# N(DAV)TableWidget
+#
+
+from PyQt4 import QtGui, QtCore
+
+try:
+    _fromUtf8 = QtCore.QString.fromUtf8
+except AttributeError:
+    _fromUtf8 = lambda s: s
+
+
+class NTableWidget(QtGui.QTableWidget):
+    """
+    NdavTableWidget inherits from QTableWidget by extending the features
+    for easy application.
+    """
+    def __init__(self, parent):
+        """
+
+        :param parent:
+        :return:
+        """
+        QtGui.QTableWidget.__init__(self, parent)
+
+        self._myParent = parent
+
+        self._myHeaderList = None
+        self._myColumnTypeList = None
+
+        return
+
+    def append_row(self, row_value_list, type_list=None):
+        """
+
+        :param row_value_list:
+        :return:
+        """
+        # Check input
+        assert isinstance(row_value_list, list)
+        if type_list is not None:
+            assert isinstance(type_list, list)
+            assert len(row_value_list) == len(type_list)
+        else:
+            type_list = self._myColumnTypeList
+        if len(row_value_list) != self.columnCount():
+            ret_msg = 'Input number of values (%d) is different from ' \
+                      'column number (%d).' % (len(row_value_list), self.columnCount())
+            return False, ret_msg
+        else:
+            ret_msg = ''
+
+        # Insert new row
+        row_number = self.rowCount()
+        self.insertRow(row_number)
+
+        # Set values
+        for i_col in xrange(min(len(row_value_list), self.columnCount())):
+            item = QtGui.QTableWidgetItem()
+            item.setText(_fromUtf8(str(row_value_list[i_col])))
+            item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable)
+            if type_list[i_col] == 'checkbox':
+                self.set_check_box(row_number, i_col, False)
+            else:
+                self.setItem(row_number, i_col, item)
+        # END-FOR(i_col)
+
+        return True, ret_msg
+
+    def get_selected_rows(self):
+        """
+
+        :return:
+        """
+        rows_list = list()
+        index_status = self._myColumnTypeList.index('checkbox')
+        for i_row in xrange(self.rowCount()):
+            is_checked = self.get_row_value(i_row)[index_status]
+            if is_checked:
+                rows_list.append(i_row)
+
+        return rows_list
+
+    def get_cell_value(self, row_index, col_index):
+        """
+
+        :param row_index:
+        :param col_index:
+        :return:
+        """
+        c_type = self._myColumnTypeList[col_index]
+
+        return_value = None
+        if c_type == 'checkbox':
+            # Check box
+            cell_i_j = self.cellWidget(row_index, col_index)
+            assert isinstance(cell_i_j, QtGui.QCheckBox)
+            return_value = cell_i_j.isChecked()
+        else:
+            # Regular cell
+            item_i_j = self.item(row_index, col_index)
+            assert isinstance(item_i_j, QtGui.QTableWidgetItem)
+            value = str(item_i_j.text())
+            if c_type == 'int':
+                return_value = int(value)
+            elif c_type == 'float':
+                return_value = float(value)
+
+        return return_value
+
+    def get_row_value(self, row_index):
+        """
+        :param row_index:
+        :return: list of objects
+        """
+        if row_index < 0 or row_index >= self.rowCount():
+            raise IndexError('Index of row (%d) is out of range.' % row_index)
+
+        ret_list = list()
+        for i_col in xrange(len(self._myColumnTypeList)):
+            c_type = self._myColumnTypeList[i_col]
+
+            if c_type == 'checkbox':
+                # Check box
+                cell_i_j = self.cellWidget(row_index, i_col)
+                assert isinstance(cell_i_j, QtGui.QCheckBox)
+                is_checked = cell_i_j.isChecked()
+                ret_list.append(is_checked)
+            else:
+                # Regular cell
+                item_i_j = self.item(row_index, i_col)
+                assert isinstance(item_i_j, QtGui.QTableWidgetItem)
+                value = str(item_i_j.text())
+                if c_type == 'int':
+                    value = int(value)
+                elif c_type == 'float':
+                    value = float(value)
+
+                ret_list.append(value)
+            # END-IF-ELSE
+        # END-FOR
+
+        return ret_list
+
+    def init_setup(self, column_tup_list):
+        """ Initial setup
+        :param column_tup_list: list of 2-tuple as string (column name) and string (data type)
+        :return:
+        """
+        print '[DB] Init set up table with %d columns!' % len(column_tup_list)
+
+        # Define column headings
+        num_cols = len(column_tup_list)
+
+        # Class variables
+        self._myHeaderList = list()
+        self._myColumnTypeList = list()
+
+        for c_tup in column_tup_list:
+            c_name = c_tup[0]
+            c_type = c_tup[1]
+            self._myHeaderList.append(c_name)
+            self._myColumnTypeList.append(c_type)
+
+        self.setColumnCount(num_cols)
+        self.setHorizontalHeaderLabels(self._myHeaderList)
+
+        return
+
+    def init_size(self, num_rows, num_cols):
+        """
+
+        :return:
+        """
+        self.setColumnCount(num_cols)
+        self.setRowCount(num_rows)
+
+        return
+
+    def set_check_box(self, row, col, state):
+        """ function to add a new select checkbox to a cell in a table row
+        won't add a new checkbox if one already exists
+        """
+        # Check input
+        assert isinstance(state, bool)
+
+        # Check if cellWidget exists
+        if self.cellWidget(row,col):
+            # existing: just set the value
+            self.cellWidget(row, col).setChecked(state)
+        else:
+            # case to add checkbox
+            checkbox = QtGui.QCheckBox()
+            checkbox.setText('')
+            checkbox.setChecked(state)
+
+            # Adding a widget which will be inserted into the table cell
+            # then centering the checkbox within this widget which in turn,
+            # centers it within the table column :-)
+            self.setCellWidget(row, col, checkbox)
+        # END-IF-ELSE
+
+        return
+
+    def set_value_cell(self, row, col, value=''):
+        """
+        Set value to a cell with integer, float or string
+        :param row:
+        :param col:
+        :param value:
+        :return:
+        """
+        # Check
+        if row < 0 or row >= self.rowCount() or col < 0 or col >= self.columnCount():
+            raise IndexError('Input row number or column number is out of range.')
+
+        # Init cell
+        cell_item = QtGui.QTableWidgetItem()
+        cell_item.setText(_fromUtf8(str(value)))
+        cell_item.setFlags(cell_item.flags() & ~QtCore.Qt.ItemIsEditable)
+
+        self.setItem(row, col, cell_item)
+
+        return
+
+    def update_cell_value(self, row, col, value):
+        """
+
+        :param row:
+        :param col:
+        :param value:
+        :return:
+        """
+        cell_item = self.item(row, col)
+        cell_widget = self.cellWidget(row, col)
+
+        if cell_item is not None and cell_widget is None:
+            # TableWidgetItem
+            assert isinstance(cell_item, QtGui.QTableWidgetItem)
+            if isinstance(value, float):
+                cell_item.setText(_fromUtf8('%.7f' % value))
+            else:
+                cell_item.setText(_fromUtf8(str(value)))
+        elif cell_item is None and cell_widget is not None:
+            # TableCellWidget
+            if isinstance(cell_item, QtGui.QCheckBox) is True:
+                cell_item.setChecked(value)
+            else:
+                raise TypeError('Cell of type %s is not supported.' % str(type(cell_item)))
+        else:
+            raise TypeError('Table cell (%d, %d) is in an unsupported situation!' % (row, col))
+
+        return
diff --git a/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py
new file mode 100644
index 00000000000..35dc1fe387e
--- /dev/null
+++ b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py
@@ -0,0 +1,188 @@
+#pylint: disable=W0633,too-many-branches
+__author__ = 'wzz'
+
+import os
+import urllib2
+import socket
+
+
+def check_url(url, read_lines=False):
+    """ Check whether a URL is valid
+    :param url:
+    :return: boolean, error message
+    """
+    lines = None
+    try:
+        # Access URL
+        url_stream = urllib2.urlopen(url, timeout=2)
+
+        # Read lines
+        if read_lines is True:
+            lines = url_stream.readlines()
+    except urllib2.URLError as url_error:
+        url_stream = url_error
+    except socket.timeout:
+        return False, 'Time out. Try again!'
+
+    # Return result
+    if url_stream.code in (200, 401):
+        url_good = True
+    else:
+        url_good = False
+
+    # Close connect
+    url_stream.close()
+
+    # Return
+    if read_lines is True:
+        return url_good, lines
+    if url_good is False:
+        error_message = 'Unable to access %s.  Check internet access. Code %d' % (url, url_stream.code)
+    else:
+        error_message = ''
+
+    return url_good, error_message
+
+
+def get_scans_list(server_url, exp_no, return_list=False):
+    """ Get list of scans under one experiment
+    :param server_url:
+    :param exp_no:
+    :return: message
+    """
+    if server_url.endswith('/') is False:
+        server_url = '%s/' % server_url
+    data_dir_url = '%sexp%d/Datafiles' % (server_url, exp_no)
+
+    does_exist, raw_lines = check_url(data_dir_url, read_lines=True)
+    if does_exist is False:
+        return "Experiment %d's URL %s cannot be found." % (exp_no, data_dir_url)
+
+    # Scan through the index page
+    scan_list = []
+    header = 'HB3A_exp%04d_scan' % exp_no
+    for line in raw_lines:
+        if line.count(header) > 0:
+            # try to find file HB3A_exp0123_scan6789.dat
+            term = line.split(header)[1].split('.dat')[0]
+            scan = int(term)
+            # check
+            if '%04d' % scan == term:
+                scan_list.append(scan)
+    # END_FOR
+    scan_list = sorted(scan_list)
+    if return_list is True:
+        return scan_list
+
+    message = 'Experiment %d: Scan from %d to %d' % (exp_no, scan_list[0], scan_list[-1])
+
+    return message
+
+
+def get_scans_list_local_disk(local_dir, exp_no):
+    """ Get scans from a specified directory on local disk
+    :param local_dir:
+    :param exp_no:
+    :return:
+    """
+    scan_list = []
+
+    file_names = os.listdir(local_dir)
+    header = 'HB3A_exp%04d_scan' % exp_no
+    for name in file_names:
+        if name.count(header) > 0:
+            scan = int(name.split(header)[1].split('.dat')[0])
+            scan_list.append(scan)
+
+    scan_list = sorted(scan_list)
+
+    if len(scan_list) == 0:
+        message = 'Experiment %d: No scan can be found.' % exp_no
+    else:
+        message = 'Experiment %d: Scan from %d to %d ' % (exp_no, scan_list[0], scan_list[-1])
+        num_skip_scans = scan_list[-1] - scan_list[0] + 1 - len(scan_list)
+        if num_skip_scans > 0:
+            message += 'with %d ' % num_skip_scans
+        else:
+            message += 'without '
+        message += 'missing scans.'
+
+    return message
+
+
+def parse_int_array(int_array_str):
+    """ Validate whether the string can be divided into integer strings.
+    Allowed: a, b, c-d, e, f
+    :param int_array_str:
+    :return:
+    """
+    int_array_str = str(int_array_str)
+    if int_array_str == "":
+        return True, []
+
+    # Split by ","
+    term_level_0 = int_array_str.split(",")
+    integer_list = []
+
+    # For each term
+    err_msg = ""
+    ret_status = True
+
+    for level0_term in term_level_0:
+        level0_term = level0_term.strip()
+
+        # split upon dash -
+        num_dashes = level0_term.count("-")
+        if num_dashes == 0:
+            # one integer
+            value_str = level0_term
+            try:
+                int_value = int(value_str)
+                if str(int_value) != value_str:
+                    ret_status = False
+                    err_msg =  "Contains non-integer string %s." % value_str
+            except ValueError:
+                ret_status = False
+                err_msg = "String %s is not an integer." % (value_str)
+            else:
+                integer_list.append(int_value)
+
+        elif num_dashes == 1:
+            # Integer range
+            two_terms = level0_term.split("-")
+            temp_list = []
+            for i in xrange(2):
+                value_str = two_terms[i]
+                try:
+                    int_value = int(value_str)
+                    if str(int_value) != value_str:
+                        ret_status = False
+                        err_msg = "Contains non-integer string %s." % (value_str)
+                except ValueError:
+                    ret_status = False
+                    err_msg = "String %s is not an integer." % (value_str)
+                else:
+                    temp_list.append(int_value)
+
+                # break loop
+                if ret_status is False:
+                    break
+            # END_FOR(i)
+            integer_list.extend(range(temp_list[0], temp_list[1]+1))
+
+        else:
+            # Undefined situation
+            ret_status = False
+            err_msg = "Term %s contains more than 1 dash." % level0_term
+        # END-IF-ELSE
+
+        # break loop if something is wrong
+        if ret_status is False:
+            break
+    # END-FOR(level0_term)
+
+    # Return with false
+    if ret_status is False:
+        return False, err_msg
+
+    return True, integer_list
diff --git a/scripts/HFIR_4Circle_Reduction/guiutility.py b/scripts/HFIR_4Circle_Reduction/guiutility.py
new file mode 100644
index 00000000000..386141d4e44
--- /dev/null
+++ b/scripts/HFIR_4Circle_Reduction/guiutility.py
@@ -0,0 +1,169 @@
+#
+# GUI Utility Methods
+#
+from PyQt4 import QtGui
+
+
+def parse_float_array(array_str):
+    """ Parse a string to an array of float
+    :param array_str:
+    :return: boolean, list of floats/error message
+    """
+    print array_str
+    assert isinstance(array_str, str)
+    array_str = array_str.replace(',', ' ')
+    array_str = array_str.replace('\n', ' ')
+    array_str = array_str.replace('\t ', ' ')
+    array_str = array_str.strip()
+    print '[DB] After processing: ', array_str
+
+    float_str_list = array_str.split()
+    float_list = list()
+    for float_str in float_str_list:
+        try:
+            value = float(float_str)
+        except ValueError as value_error:
+            return False, 'Unable to parse %s due to %s.' % (float_str, str(value_error))
+        else:
+            float_list.append(value)
+    # END-FOR
+
+    return True, float_list
+
+
+def parse_integer_list(array_str):
+    """ Parse a string to an array of integer separated by ','
+    also, the format as 'a-b' is supported too
+    :param array_str:
+    :return: boolean, list of floats/error message
+    """
+    assert isinstance(array_str, str)
+    array_str = array_str.replace(' ', '')
+    array_str = array_str.replace('\n', '')
+    array_str = array_str.replace('\t ', '')
+
+    int_str_list = array_str.split(',')
+    int_list = list()
+    for int_str in int_str_list:
+
+        try:
+            int_value = int(int_str)
+            int_list.append(int_value)
+        except ValueError:
+            num_dash = int_str.count('-')
+            if num_dash == 1:
+                terms = int_str.split('-')
+                try:
+                    start_value = int(terms[0])
+                    end_value = int(terms[1])
+                except ValueError:
+                    raise 'Unable to parse %s due to value error' % int_str
+            elif num_dash == 2 and int_str.startswith('-'):
+                terms = int_str[1:].split('-')
+                try:
+                    start_value = int(terms[0])*-1
+                    end_value = int(terms[1])
+                except ValueError:
+                    raise 'Unable to parse %s due to value error' % int_str
+            elif num_dash == 3:
+                terms = int_str.split('-')
+                try:
+                    start_value = -1*int(terms[1])
+                    end_value = -1*int(terms[3])
+                except ValueError:
+                    raise 'Unable to parse %s due to value error' % int_str
+                except IndexError:
+                    raise 'Unable to parse %s due to value error' % int_str
+            else:
+                raise 'Unable to parse %s due to value error' % int_str
+
+            int_list.extend(xrange(start_value, end_value+1))
+    # END-FOR
+
+    return int_list
+
+
+def parse_float_editors(line_edits):
+    """
+    :param line_edit_list:
+    :return: (True, list of floats); (False, error message)
+    """
+    # Set flag
+    return_single_value = False
+
+    if isinstance(line_edits, QtGui.QLineEdit) is True:
+        line_edit_list = [line_edits]
+        return_single_value = True
+    elif isinstance(line_edits, list) is True:
+        line_edit_list = line_edits
+    else:
+        raise RuntimeError('Input is not LineEdit or list of LineEdit.')
+
+    error_message = ''
+    float_list = []
+
+    for line_edit in line_edit_list:
+        assert isinstance(line_edit, QtGui.QLineEdit)
+        try:
+            str_value = str(line_edit.text()).strip()
+            float_value = float(str_value)
+        except ValueError as value_err:
+            error_message += 'Unable to parse to integer. %s\n' % (str(value_err))
+        else:
+            float_list.append(float_value)
+        # END-TRY
+    # END-FOR
+
+    if len(error_message) > 0:
+        return False, error_message
+    elif return_single_value is True:
+        return True, float_list[0]
+
+    return True, float_list
+
+
+def parse_integers_editors(line_edits):
+    """
+    :param line_edit_list:
+    :return: (True, list of integers); (False, error message)
+    """
+    # Set flag
+    return_single_value = False
+
+    if isinstance(line_edits, QtGui.QLineEdit) is True:
+        line_edit_list = [line_edits]
+        return_single_value = True
+    elif isinstance(line_edits, list) is True:
+        line_edit_list = line_edits
+    else:
+        raise RuntimeError('Input is not LineEdit or list of LineEdit.')
+
+    error_message = ''
+    integer_list = []
+
+    for line_edit in line_edit_list:
+        assert isinstance(line_edit, QtGui.QLineEdit)
+        try:
+            str_value = str(line_edit.text()).strip()
+            int_value = int(str_value)
+        except ValueError as value_err:
+            error_message += 'Unable to parse to integer. %s\n' % (str(value_err))
+        else:
+            if str_value != '%d' % int_value:
+                error_message += 'Value %s is not a proper integer.\n' % str_value
+            else:
+                integer_list.append(int_value)
+        # END-TRY
+    # END-FOR
+
+    if len(error_message) > 0:
+        return False, error_message
+    elif return_single_value is True:
+        return True, integer_list[0]
+
+    return True, integer_list
+
+
+if __name__ == '__main__':
+    int_list = parse_integer_list('123, -234, 330-350, 400, -2-4')
+    print int_list
diff --git a/scripts/HFIR_4Circle_Reduction/hfctables.py b/scripts/HFIR_4Circle_Reduction/hfctables.py
new file mode 100644
index 00000000000..cb1b45624fc
--- /dev/null
+++ b/scripts/HFIR_4Circle_Reduction/hfctables.py
@@ -0,0 +1,364 @@
+#pylint: disable=W0403,C0103,R0901,R0904
+import numpy
+import NTableWidget as tableBase
+
+# UB peak information table
+Peak_Integration_Table_Setup = [('Scan', 'int'),
+                                ('Pt', 'int'),
+                                ('H', 'float'),
+                                ('K', 'float'),
+                                ('L', 'float'),
+                                ('Q_x', 'float'),
+                                ('Q_y', 'float'),
+                                ('Q_z', 'float'),
+                                ('Intensity', 'float')]
+
+
+class IntegratePeaksTableWidget(tableBase.NTableWidget):
+    """
+    Extended table widget for peak integration
+    """
+    def __init__(self, parent):
+        """
+        :param parent:
+        """
+        tableBase.NTableWidget.__init__(self, parent)
+
+        return
+
+    def setup(self):
+        """
+        Init setup
+        :return:
+        """
+        self.init_setup(Peak_Integration_Table_Setup)
+
+        return
+
+
+class UBMatrixTable(tableBase.NTableWidget):
+    """
+    Extended table for UB matrix
+    """
+    def __init__(self, parent):
+        """
+
+        :param parent:
+        :return:
+        """
+        tableBase.NTableWidget.__init__(self, parent)
+
+        # Matrix
+        self._matrix = numpy.ndarray((3, 3), float)
+        for i in xrange(3):
+            for j in xrange(3):
+                self._matrix[i][j] = 0.
+
+        return
+
+    def _set_to_table(self):
+        """
+        TODO/DOC
+        :return:
+        """
+        for i_row in xrange(3):
+            for j_col in xrange(3):
+                self.update_cell_value(i_row, j_col, self._matrix[i_row][j_col])
+
+        return
+
+    def get_matrix(self):
+        """
+        Get the copy of the matrix
+        :return:
+        """
+        print '[DB] MatrixTable: _Matrix = ', self._matrix
+        return self._matrix.copy()
+
+    def set_from_list(self, element_array):
+        """
+        TODO/DOC
+        :param element_array:
+        :return:
+        """
+        # Check
+        assert isinstance(element_array, list)
+        assert len(element_array) == 9
+
+        # Set value
+        i_array = 0
+        for i in xrange(3):
+            for j in xrange(3):
+                self._matrix[i][j] = element_array[i_array]
+                i_array += 1
+
+        # Set to table
+        self._set_to_table()
+
+        return
+
+    def set_from_matrix(self, matrix):
+        """
+        TODO - DOC
+        :param matrix:
+        :return:
+        """
+        # Check
+        assert isinstance(matrix, numpy.ndarray)
+        assert matrix.shape == (3, 3)
+
+        for i in xrange(3):
+            for j in xrange(3):
+                self._matrix[i][j] = matrix[i][j]
+
+        self._set_to_table()
+
+        return
+
+    def setup(self):
+        """
+        Init setup
+        :return:
+        """
+        # self.init_size(3, 3)
+
+        for i in xrange(3):
+            for j in xrange(3):
+                self.set_value_cell(i, j)
+
+        self._set_to_table()
+
+        return
+
+
+# UB peak information table
+UB_Peak_Table_Setup = [('Scan', 'int'),
+                       ('Pt', 'int'),
+                       ('H', 'float'),
+                       ('K', 'float'),
+                       ('L', 'float'),
+                       ('Q_x', 'float'),
+                       ('Q_y', 'float'),
+                       ('Q_z', 'float'),
+                       ('Use', 'checkbox'),
+                       ('m1', 'float'),
+                       ('Error', 'float')]
+
+
+class UBMatrixPeakTable(tableBase.NTableWidget):
+    """
+    Extended table for peaks used to calculate UB matrix
+    """
+    def __init__(self, parent):
+        """
+
+        :param parent:
+        :return:
+        """
+        tableBase.NTableWidget.__init__(self, parent)
+
+        return
+
+    def get_exp_info(self, row_index):
+        """
+        Get experiment information from a row
+        :return: scan number, pt number
+        """
+        assert isinstance(row_index, int)
+
+        scan_number = self.get_cell_value(row_index, 0)
+        assert isinstance(scan_number, int)
+        pt_number = self.get_cell_value(row_index, 1)
+        assert isinstance(pt_number, int)
+
+        return scan_number, pt_number
+
+    def get_hkl(self, row_index):
+        """
+        Get reflection's miller index
+        :param row_index:
+        :return:
+        """
+        assert isinstance(row_index, int)
+
+        m_h = self.get_cell_value(row_index, 2)
+        m_k = self.get_cell_value(row_index, 3)
+        m_l = self.get_cell_value(row_index, 4)
+
+        assert isinstance(m_h, float)
+        assert isinstance(m_k, float)
+        assert isinstance(m_l, float)
+
+        return m_h, m_k, m_l
+
+    def is_selected(self, row_index):
+        """
+
+        :return:
+        """
+        if row_index < 0 or row_index >= self.rowCount():
+            raise IndexError('Input row number %d is out of range [0, %d)' % (row_index, self.rowCount()))
+
+        col_index = UB_Peak_Table_Setup.index(('Use', 'checkbox'))
+
+        return self.get_cell_value(row_index, col_index)
+
+    def setup(self):
+        """
+        Init setup
+        :return:
+        """
+        self.init_setup(UB_Peak_Table_Setup)
+
+        return
+
+    def set_hkl(self, i_row, hkl, error=None):
+        """
+        Set HKL to table
+        :param irow:
+        :param hkl:
+        """
+        # Check
+        assert isinstance(i_row, int)
+        assert isinstance(hkl, list)
+
+        i_col_h = UB_Peak_Table_Setup.index(('H', 'float'))
+        i_col_k = UB_Peak_Table_Setup.index(('K', 'float'))
+        i_col_l = UB_Peak_Table_Setup.index(('L', 'float'))
+
+        self.update_cell_value(i_row, i_col_h, hkl[0])
+        self.update_cell_value(i_row, i_col_k, hkl[1])
+        self.update_cell_value(i_row, i_col_l, hkl[2])
+
+        if error is not None:
+            i_col_error = UB_Peak_Table_Setup.index(('Error', 'float'))
+            self.update_cell_value(i_row, i_col_error, error)
+
+        return
+
+# Processing status table
+Process_Table_Setup = [('Scan', 'int'),
+                       ('Number Pt', 'int'),
+                       ('Status', 'str'),
+                       ('Merged Workspace', 'str'),
+                       ('Group Name', 'str'),
+                       ('Select', 'checkbox')]
+
+
+class ProcessTableWidget(tableBase.NTableWidget):
+    """
+    Extended table for peaks used to calculate UB matrix
+    """
+    def __init__(self, parent):
+        """
+
+        :param parent:
+        :return:
+        """
+        tableBase.NTableWidget.__init__(self, parent)
+
+        return
+
+    def append_scans(self, scans):
+        """ Append rows
+        :param scans:
+        :return:
+        """
+        # Check
+        assert isinstance(scans, list)
+
+        # Append rows
+        for scan in scans:
+            row_value_list = [scan, 0, 'In Queue', '', '', False]
+            status, err = self.append_row(row_value_list)
+            if status is False:
+                raise RuntimeError(err)
+
+        return
+
+    def setup(self):
+        """
+        Init setup
+        :return:
+        """
+        self.init_setup(Process_Table_Setup)
+
+        return
+
+    def set_scan_pt(self, scan_no, pt_list):
+        """
+        :param scan_no:
+        :param pt_list:
+        :return:
+        """
+        # Check
+        assert isinstance(scan_no, int)
+
+        num_rows = self.rowCount()
+        set_done = False
+        for i_row in xrange(num_rows):
+            tmp_scan_no = self.get_cell_value(i_row, 0)
+            if scan_no == tmp_scan_no:
+                self.update_cell_value(i_row, 1, len(pt_list))
+                set_done = True
+                break
+        # END-FOR
+
+        if set_done is False:
+            return 'Unable to find scan %d in table.' % scan_no
+
+        return ''
+
+    def set_status(self, scan_no, status):
+        """
+        TODO/Doc
+        :param status:
+        :return:
+        """
+        # Check
+        assert isinstance(scan_no, int)
+
+        num_rows = self.rowCount()
+        set_done = False
+        for i_row in xrange(num_rows):
+            tmp_scan_no = self.get_cell_value(i_row, 0)
+            if scan_no == tmp_scan_no:
+                self.update_cell_value(i_row, 2, status)
+                set_done = True
+                break
+        # END-FOR
+
+        if set_done is False:
+            return 'Unable to find scan %d in table.' % scan_no
+
+        return ''
+
+    def set_ws_names(self, scan_num, merged_md_name, ws_group_name):
+        """
+        TODO/DOC
+        :param merged_md_name:
+        :param ws_group_name:
+        :return:
+        """
+        # Check
+        assert isinstance(scan_num, int)
+        assert isinstance(merged_md_name, str) or merged_md_name is None
+        assert isinstance(ws_group_name, str) or ws_group_name is None
+
+        num_rows = self.rowCount()
+        set_done = False
+        for i_row in xrange(num_rows):
+            tmp_scan_no = self.get_cell_value(i_row, 0)
+            if scan_num == tmp_scan_no:
+                if merged_md_name is not None:
+                    self.update_cell_value(i_row, 3, merged_md_name)
+                if ws_group_name is not None:
+                    self.update_cell_value(i_row, 4, ws_group_name)
+                set_done = True
+                break
+        # END-FOR
+
+        if set_done is False:
+            return 'Unable to find scan %d in table.' % scan_num
+
+        return
diff --git a/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py b/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py
new file mode 100644
index 00000000000..152ffc29c47
--- /dev/null
+++ b/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py
@@ -0,0 +1,1255 @@
+#pylint: disable=invalid-name,too-many-public-methods,too-many-arguments,non-parent-init-called,R0902,too-many-branches,C0302
+import os
+import numpy as np
+
+from PyQt4 import QtGui
+
+from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar2
+from matplotlib.figure import Figure
+import matplotlib.image
+
+MplLineStyles = ['-' , '--' , '-.' , ':' , 'None' , ' ' , '']
+MplLineMarkers = [
+    ". (point         )",
+    "* (star          )",
+    "x (x             )",
+    "o (circle        )",
+    "s (square        )",
+    "D (diamond       )",
+    ", (pixel         )",
+    "v (triangle_down )",
+    "^ (triangle_up   )",
+    "< (triangle_left )",
+    "> (triangle_right)",
+    "1 (tri_down      )",
+    "2 (tri_up        )",
+    "3 (tri_left      )",
+    "4 (tri_right     )",
+    "8 (octagon       )",
+    "p (pentagon      )",
+    "h (hexagon1      )",
+    "H (hexagon2      )",
+    "+ (plus          )",
+    "d (thin_diamond  )",
+    "| (vline         )",
+    "_ (hline         )",
+    "None (nothing    )"]
+
+# Note: in colors, "white" is removed
+MplBasicColors = [
+    "black",
+    "red",
+    "blue",
+    "green",
+    "cyan",
+    "magenta",
+    "yellow"]
+
+
+class IndicatorManager(object):
+    """ Manager for all indicator lines
+    """
+    def __init__(self):
+        """
+
+        :return:
+        """
+        # Auto color index
+        self._colorIndex = 0
+        # Auto line ID
+        self._autoLineID = 1
+
+        self._lineManager = dict()
+        self._canvasLineKeyDict = dict()
+        self._indicatorTypeDict = dict()  # value: 0 (horizontal), 1 (vertical), 2 (2-way)
+
+        return
+
+    def add_2way_indicator(self, x, x_min, x_max, y, y_min, y_max, color):
+        """
+
+        :param x:
+        :param x_min:
+        :param x_max:
+        :param y:
+        :param y_min:
+        :param y_max:
+        :param color:
+        :return:
+        """
+        # Set up indicator ID
+        this_id = str(self._autoLineID)
+        self._autoLineID += 1
+
+        # Set up vectors
+        vec_x_horizontal = np.array([x_min, x_max])
+        vec_y_horizontal = np.array([y, y])
+
+        vec_x_vertical = np.array([x, x])
+        vec_y_vertical = np.array([y_min, y_max])
+
+        #
+        self._lineManager[this_id] = [vec_x_horizontal, vec_y_horizontal, vec_x_vertical, vec_y_vertical, color]
+        self._indicatorTypeDict[this_id] = 2
+
+        return this_id
+
+    def add_horizontal_indicator(self, y, x_min, x_max, color):
+        """
+        Add a horizontal indicator
+        :param y:
+        :param x_min:
+        :param x_max:
+        :param color:
+        :return:
+        """
+        # Get ID
+        this_id = str(self._autoLineID)
+        self._autoLineID += 1
+
+        #
+        vec_x = np.array([x_min, x_max])
+        vec_y = np.array([y, y])
+
+        #
+        self._lineManager[this_id] = [vec_x, vec_y, color]
+        self._indicatorTypeDict[this_id] = 0
+
+        return this_id
+
+    def add_vertical_indicator(self, x, y_min, y_max, color):
+        """
+        Add a vertical indicator to data structure
+        :return: indicator ID
+        """
+        # Get ID
+        this_id = str(self._autoLineID)
+        self._autoLineID += 1
+
+        #
+        vec_x = np.array([x, x])
+        vec_y = np.array([y_min, y_max])
+
+        #
+        self._lineManager[this_id] = [vec_x, vec_y, color]
+        self._indicatorTypeDict[this_id] = 1
+
+        return this_id
+
+    def get_canvas_line_index(self, my_id):
+        """
+
+        :param my_id:
+        :return:
+        """
+        assert isinstance(my_id, str)
+
+        if my_id not in self._canvasLineKeyDict:
+            raise RuntimeError('Indicator ID %s cannot be found. Current keys are %s.' % (
+                my_id, str(sorted(self._canvasLineKeyDict.keys()))
+            ))
+        return self._canvasLineKeyDict[my_id]
+
+    def get_line_type(self, my_id):
+        """
+
+        :param my_id:
+        :return:
+        """
+        return self._indicatorTypeDict[my_id]
+
+    def get_2way_data(self, line_id):
+        """
+
+        :param line_id:
+        :return:
+        """
+        assert self._indicatorTypeDict.has_key(line_id)
+        assert self._indicatorTypeDict[line_id] == 2
+
+        vec_set = [self._lineManager[line_id][0:2], self._lineManager[line_id][2:4]]
+
+        return vec_set
+
+    def get_data(self, line_id):
+        """
+        Get line's vector x and vector y
+        :param line_id:
+        :return:
+        """
+        return self._lineManager[line_id][0], self._lineManager[line_id][1]
+
+    def get_line_style(self, line_id=None):
+        """
+
+        :param line_id:
+        :return:
+        """
+        assert isinstance(line_id, None)
+        return '--'
+
+    def get_live_indicator_ids(self):
+        """
+
+        :return:
+        """
+        return sorted(self._lineManager.keys())
+
+    def get_marker(self):
+        """
+        Get the marker a line
+        :param line_id:
+        :return:
+        """
+        return 'o'
+
+    def get_next_color(self):
+        """
+        Get next color by auto color index
+        :return: string as color
+        """
+        next_color = MplBasicColors[self._colorIndex]
+
+        # Advance and possibly reset color scheme
+        self._colorIndex += 1
+        if self._colorIndex == len(MplBasicColors):
+            self._colorIndex = 0
+
+        return next_color
+
+    def set_canvas_line_index(self, my_id, canvas_line_index):
+        """
+
+        :param my_id:
+        :param canvas_line_index:
+        :return:
+        """
+        self._canvasLineKeyDict[my_id] = canvas_line_index
+
+    def shift(self, my_id, dx, dy):
+        """
+
+        :param my_id:
+        :param dx:
+        :param dy:
+        :return:
+        """
+        print self._lineManager[my_id][0]
+
+        if self._indicatorTypeDict[my_id] == 0:
+            # horizontal
+            self._lineManager[my_id][1] += dy
+
+        elif self._indicatorTypeDict[my_id] == 1:
+            # vertical
+            self._lineManager[my_id][0] += dx
+
+        elif self._indicatorTypeDict[my_id] == 2:
+            # 2-way
+            self._lineManager[my_id][2] += dx
+            self._lineManager[my_id][1] += dy
+
+        else:
+            raise RuntimeError('Unsupported indicator of type %d' % self._indicatorTypeDict[my_id])
+
+        return
+
+    def update_indicators_range(self, x_range, y_range):
+        """
+        Update indicator's range
+        :param x_range:
+        :param y_range:
+        :return:
+        """
+        for i_id in self._lineManager.keys():
+            # NEXT - Need a new flag for direction of the indicating line, vertical or horizontal
+            if True:
+                self._lineManager[i_id][1][0] = y_range[0]
+                self._lineManager[i_id][1][-1] = y_range[1]
+            else:
+                self._lineManager[i_id][0][0] = x_range[0]
+                self._lineManager[i_id][0][-1] = x_range[1]
+
+        return
+
+
+class MplGraphicsView(QtGui.QWidget):
+    """ A combined graphics view including matplotlib canvas and
+    a navigation tool bar
+
+    Note: Merged with HFIR_Powder_Reduction.MplFigureCAnvas
+    """
+    def __init__(self, parent):
+        """ Initialization
+        """
+        # Initialize parent
+        QtGui.QWidget.__init__(self, parent)
+
+        # set up canvas
+        self._myCanvas = Qt4MplCanvas(self)
+        self._myToolBar = MyNavigationToolbar(self, self._myCanvas)
+
+        # set up layout
+        self._vBox = QtGui.QVBoxLayout(self)
+        self._vBox.addWidget(self._myCanvas)
+        self._vBox.addWidget(self._myToolBar)
+
+        # auto line's maker+color list
+        self._myLineMarkerColorList = []
+        self._myLineMarkerColorIndex = 0
+        self.setAutoLineMarkerColorCombo()
+
+        # Declaration of class variables
+        self._indicatorKey = None
+
+        # Indicator manager
+        self._myIndicatorsManager = IndicatorManager()
+
+        return
+
+    def add_line_set(self, vec_set, color, marker, line_style, line_width):
+        """ Add a set of line and manage together
+        :param vec_set:
+        :param color:
+        :param marker:
+        :param line_style:
+        :param line_width:
+        :return:
+        """
+        key_list = list()
+        for vec_x, vec_y in vec_set:
+            temp_key = self._myCanvas.add_plot_1d(vec_x, vec_y, color=color, marker=marker,
+                                                  line_style=line_style, line_width=line_width)
+            assert isinstance(temp_key, int)
+            assert temp_key >= 0
+            key_list.append(temp_key)
+
+        return key_list
+
+    def add_plot_1d(self, vec_x, vec_y, y_err=None, color=None, label="", x_label=None, y_label=None,
+                    marker=None, line_style=None, line_width=1):
+        """ Add a new plot
+        """
+        line_key = self._myCanvas.add_plot_1d(vec_x, vec_y, y_err, color, label, x_label, y_label, marker, line_style,
+                                              line_width)
+
+        return line_key
+
+    def add_plot_1d_right(self, vec_x, vec_y, color=None, label='', marker=None, line_style=None, line_width=1):
+        """
+        Add 1 line (1-d plot) to right axis
+        :param vec_x:
+        :param vec_y:
+        :param color:
+        :param label:
+        :param marker:
+        :param line_style:
+        :param line_width:
+        :return:
+        """
+        line_key = self._myCanvas.add_1d_plot_right(vec_x, vec_y, label=label,
+                                                    color=color, marker=marker,
+                                                    linestyle=line_style, linewidth=line_width)
+
+        return line_key
+
+    def add_2way_indicator(self, x=None, y=None, color=None, master_line=None):
+        """ Add a 2-way indicator following an existing line?
+        :param x:
+        :param y:
+        :param color:
+        :return:
+        """
+        if master_line is not None:
+            raise RuntimeError('Implement how to use master_line ASAP.')
+
+        x_min, x_max = self._myCanvas.getXLimit()
+        if x is None:
+            x = (x_min + x_max) * 0.5
+        else:
+            assert isinstance(x, float)
+
+        y_min, y_max = self._myCanvas.getYLimit()
+        if y is None:
+            y = (y_min + y_max) * 0.5
+        else:
+            assert isinstance(y, float)
+
+        if color is None:
+            color = self._myIndicatorsManager.get_next_color()
+        else:
+            assert isinstance(color, str)
+
+        my_id = self._myIndicatorsManager.add_2way_indicator(x, x_min, x_max,
+                                                             y, y_min, y_max,
+                                                             color)
+        vec_set = self._myIndicatorsManager.get_2way_data(my_id)
+
+        canvas_line_index = self.add_line_set(vec_set, color=color,
+                                              marker=self._myIndicatorsManager.get_marker(),
+                                              line_style=self._myIndicatorsManager.get_line_style(),
+                                              line_width=1)
+        self._myIndicatorsManager.set_canvas_line_index(my_id, canvas_line_index)
+
+        return my_id
+
+    def add_horizontal_indicator(self, y=None, color=None):
+        """ Add an indicator line
+        """
+        # Default
+        if y is None:
+            y_min, y_max = self._myCanvas.getYLimit()
+            y = (y_min + y_max) * 0.5
+        else:
+            assert isinstance(y, float)
+
+        x_min, x_max = self._myCanvas.getXLimit()
+
+        # For color
+        if color is None:
+            color = self._myIndicatorsManager.get_next_color()
+        else:
+            assert isinstance(color, str)
+
+        # Form
+        my_id = self._myIndicatorsManager.add_horizontal_indicator(y, x_min, x_max, color)
+        vec_x, vec_y = self._myIndicatorsManager.get_data(my_id)
+
+        canvas_line_index = self._myCanvas.add_plot_1d(vec_x=vec_x, vec_y=vec_y,
+                                                       color=color, marker=self._myIndicatorsManager.get_marker(),
+                                                       line_style=self._myIndicatorsManager.get_line_style(),
+                                                       line_width=1)
+
+        self._myIndicatorsManager.set_canvas_line_index(my_id, canvas_line_index)
+
+        return my_id
+
+    def add_vertical_indicator(self, x=None, color=None):
+        """
+        Add a vertical indicator line
+        :param x: None as the automatic mode using default from middle of canvas
+        :param color: None as the automatic mode using default
+        :return: indicator ID
+        """
+        # For indicator line's position
+        if x is None:
+            x_min, x_max = self._myCanvas.getXLimit()
+            x = (x_min + x_max) * 0.5
+        else:
+            assert isinstance(x, float)
+
+        y_min, y_max = self._myCanvas.getYLimit()
+
+        # For color
+        if color is None:
+            color = self._myIndicatorsManager.get_next_color()
+        else:
+            assert isinstance(color, str)
+
+        # Form
+        my_id = self._myIndicatorsManager.add_vertical_indicator(x, y_min, y_max, color)
+        vec_x, vec_y = self._myIndicatorsManager.get_data(my_id)
+
+        canvas_line_index = self._myCanvas.add_plot_1d(vec_x=vec_x, vec_y=vec_y,
+                                                       color=color, marker=self._myIndicatorsManager.get_marker(),
+                                                       line_style=self._myIndicatorsManager.get_line_style(),
+                                                       line_width=1)
+
+        self._myIndicatorsManager.set_canvas_line_index(my_id, canvas_line_index)
+
+        return my_id
+
+    def add_plot_2d(self, array2d, x_min, x_max, y_min, y_max, hold_prev_image=True, y_tick_label=None):
+        """
+        Add a 2D image to canvas
+        :param array2d: numpy 2D array
+        :param x_min:
+        :param x_max:
+        :param y_min:
+        :param y_max:
+        :param hold_prev_image:
+        :param y_tick_label:
+        :return:
+        """
+        self._myCanvas.addPlot2D(array2d, x_min, x_max, y_min, y_max, hold_prev_image, y_tick_label)
+
+        return
+
+
+    def addImage(self, imagefilename):
+        """ Add an image by file
+        """
+        # check
+        if os.path.exists(imagefilename) is False:
+            raise NotImplementedError("Image file %s does not exist." % (imagefilename))
+
+        self._myCanvas.addImage(imagefilename)
+
+        return
+
+    def clear_all_lines(self):
+        """
+        """
+        self._myCanvas.clear_all_1d_plots()
+
+    def clear_canvas(self):
+        """ Clear canvas
+        """
+        return self._myCanvas.clear_canvas()
+
+    def draw(self):
+        """ Draw to commit the change
+        """
+        return self._myCanvas.draw()
+
+    def evt_view_updated(self):
+        """ Event handling as canvas size updated
+        :return:
+        """
+        # update the indicator
+        new_x_range = self.getXLimit()
+        new_y_range = self.getYLimit()
+
+        self._myIndicatorsManager.update_indicators_range(new_x_range, new_y_range)
+        for indicator_key in self._myIndicatorsManager.get_live_indicator_ids():
+            canvas_line_id = self._myIndicatorsManager.get_canvas_line_index(indicator_key)
+            data_x, data_y = self._myIndicatorsManager.get_data(indicator_key)
+            self.updateLine(canvas_line_id, data_x, data_y)
+        # END-FOR
+
+        return
+
+    def getPlot(self):
+        """
+        """
+        return self._myCanvas.getPlot()
+
+    def getLastPlotIndexKey(self):
+        """ Get ...
+        """
+        return self._myCanvas.getLastPlotIndexKey()
+
+    def getXLimit(self):
+        """ Get limit of Y-axis
+        """
+        return self._myCanvas.getXLimit()
+
+    def getYLimit(self):
+        """ Get limit of Y-axis
+        """
+        return self._myCanvas.getYLimit()
+
+    def move_indicator(self, line_id, dx, dy):
+        """
+        Move the indicator line in horizontal
+        :param line_id:
+        :param dx:
+        :return:
+        """
+        # Shift value
+        self._myIndicatorsManager.shift(line_id, dx=dx, dy=dy)
+
+        # apply to plot on canvas
+        if self._myIndicatorsManager.get_line_type(line_id) < 2:
+            # horizontal or vertical
+            canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(line_id)
+            vec_x, vec_y = self._myIndicatorsManager.get_data(line_id)
+            self._myCanvas.updateLine(ikey=canvas_line_index, vecx=vec_x, vecy=vec_y)
+        else:
+            # 2-way
+            canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(line_id)
+            h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(line_id)
+
+            self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=h_vec_set[0], vecy=h_vec_set[1])
+            self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=v_vec_set[0], vecy=v_vec_set[1])
+
+        return
+
+    def remove_indicator(self, indicator_key):
+        """ Remove indicator line
+        :param indicator_key:
+        :return:
+        """
+        #
+        plot_id = self._myIndicatorsManager.get_canvas_line_index(indicator_key)
+        self._myCanvas.remove_plot_1d(plot_id)
+
+        return
+
+    def removePlot(self, ikey):
+        """
+        """
+        return self._myCanvas.remove_plot_1d(ikey)
+
+    def updateLine(self, ikey, vecx, vecy, linestyle=None, linecolor=None, marker=None, markercolor=None):
+        """
+        """
+        return self._myCanvas.updateLine(ikey, vecx, vecy, linestyle, linecolor, marker, markercolor)
+
+    def update_indicator(self, i_key, color):
+        """
+        Update indicator with new color
+        :param i_key:
+        :param vec_x:
+        :param vec_y:
+        :param color:
+        :return:
+        """
+        if self._myIndicatorsManager.get_line_type(i_key) < 2:
+            # horizontal or vertical
+            canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(i_key)
+            self._myCanvas.updateLine(ikey=canvas_line_index, vecx=None, vecy=None, linecolor=color)
+        else:
+            # 2-way
+            canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(i_key)
+            # h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(i_key)
+
+            self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=None, vecy=None, linecolor=color)
+            self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=None, vecy=None, linecolor=color)
+
+        return
+
+    def get_indicator_position(self, indicator_key):
+        """ Get position (x or y) of the indicator
+        :return:
+        """
+        # NEXT - Consider a better and more consistent return
+        vec_x, vec_y = self._myIndicatorsManager.get_data(indicator_key)
+        if vec_x[0] == vec_x[1]:
+            return vec_x[0]
+
+        return vec_y[0]
+
+    def getLineStyleList(self):
+        """
+        """
+        return MplLineStyles
+
+    def getLineMarkerList(self):
+        """
+        """
+        return MplLineMarkers
+
+    def getLineBasicColorList(self):
+        """
+        """
+        return MplBasicColors
+
+    def getDefaultColorMarkerComboList(self):
+        """ Get a list of line/marker color and marker style combination
+        as default to add more and more line to plot
+        """
+        return self._myCanvas.getDefaultColorMarkerComboList()
+
+    def getNextLineMarkerColorCombo(self):
+        """ As auto line's marker and color combo list is used,
+        get the NEXT marker/color combo
+        """
+        # get from list
+        marker, color = self._myLineMarkerColorList[self._myLineMarkerColorIndex]
+        # process marker if it has information
+        if marker.count(' (') > 0:
+            marker = marker.split(' (')[0]
+        print "[DB] Print line %d: marker = %s, color = %s" % (self._myLineMarkerColorIndex, marker, color)
+
+        # update the index
+        self._myLineMarkerColorIndex += 1
+        if self._myLineMarkerColorIndex == len(self._myLineMarkerColorList):
+            self._myLineMarkerColorIndex = 0
+
+        return marker, color
+
+    def resetLineColorStyle(self):
+        """ Reset the auto index for line's color and style
+        """
+        self._myLineMarkerColorIndex = 0
+        return
+
+    # NEXT-Urgent! - Find out difference between setXYLimit() and setXYLimits()
+    def setXYLimit(self, xmin, xmax, ymin, ymax):
+        """ Set X-Y limit automatically
+        """
+        self._myCanvas.axes.set_xlim([xmin, xmax])
+        self._myCanvas.axes.set_ylim([ymin, ymax])
+
+        self._myCanvas.draw()
+
+        return
+
+    def setXYLimits(self, xmin=None, xmax=None, ymin=None, ymax=None):
+        """
+        """
+        return self._myCanvas.setXYLimit(xmin, xmax, ymin, ymax)
+
+    def setAutoLineMarkerColorCombo(self):
+        """
+        """
+        self._myLineMarkerColorList = []
+        for marker in MplLineMarkers:
+            for color in MplBasicColors:
+                self._myLineMarkerColorList.append( (marker, color) )
+
+        return
+
+    def setLineMarkerColorIndex(self, newindex):
+        """
+        """
+        self._myLineMarkerColorIndex = newindex
+
+        return
+
+
+class Qt4MplCanvas(FigureCanvas):
+    """  A customized Qt widget for matplotlib figure.
+    It can be used to replace GraphicsView of QtGui
+    """
+    def __init__(self, parent):
+        """  Initialization
+        """
+        # from mpl_toolkits.axes_grid1 import host_subplot
+        # import mpl_toolkits.axisartist as AA
+        # import matplotlib.pyplot as plt
+
+        # Instantiating matplotlib Figure
+        self.fig = Figure()
+        self.fig.patch.set_facecolor('white')
+
+        if True:
+            self.axes = self.fig.add_subplot(111) # return: matplotlib.axes.AxesSubplot
+            self.axes2 = None
+        else:
+            self.axes = self.fig.add_host_subplot(111)
+
+        # Initialize parent class and set parent
+        FigureCanvas.__init__(self, self.fig)
+        self.setParent(parent)
+
+        # Set size policy to be able to expanding and resizable with frame
+        FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
+        FigureCanvas.updateGeometry(self)
+
+        # Variables to manage all lines/subplot
+        self._lineDict = {}
+        self._lineIndex = 0
+
+        # legend and color bar
+        self._colorBar = None
+
+        return
+
+    def add_plot_1d(self, vec_x, vec_y, y_err=None, color=None, label="", x_label=None, y_label=None,
+                    marker=None, line_style=None, line_width=1):
+        """
+
+        :param vec_x: numpy array X
+        :param vec_y: numpy array Y
+        :param y_err:
+        :param color:
+        :param label:
+        :param x_label:
+        :param y_label:
+        :param marker:
+        :param line_style:
+        :param line_width:
+        :return: new key
+        """
+        # Check input
+        if isinstance(vec_x, np.ndarray) is False or isinstance(vec_y, np.ndarray) is False:
+            raise NotImplementedError('Input vec_x or vec_y for addPlot() must be numpy.array.')
+        plot_error = y_err is not None
+        if plot_error is True:
+            if isinstance(y_err, np.ndarray) is False:
+                raise NotImplementedError('Input y_err must be either None or numpy.array.')
+
+        if len(vec_x) != len(vec_y):
+            raise NotImplementedError('Input vec_x and vec_y must have same size.')
+        if plot_error is True and len(y_err) != len(vec_x):
+            raise NotImplementedError('Input vec_x, vec_y and y_error must have same size.')
+
+        # Hold previous data
+        self.axes.hold(True)
+
+        # process inputs and defaults
+        if color is None:
+            color = (0,1,0,1)
+        if marker is None:
+            marker = 'o'
+        if line_style is None:
+            line_style = '-'
+
+        # color must be RGBA (4-tuple)
+        if plot_error is False:
+            print "[DB] line_style = ", line_style, "line_width = ", line_width, "marker = ", marker, "color = ", color
+            r = self.axes.plot(vec_x, vec_y, color=color, marker=marker, linestyle=line_style,
+                               label=label, linewidth=line_width)
+            # return: list of matplotlib.lines.Line2D object
+        else:
+            r = self.axes.errorbar(vec_x, vec_y, yerr=y_err, color=color, marker=marker, linestyle=line_style,
+                                   label=label, linewidth=line_width)
+
+        self.axes.set_aspect('auto')
+
+        # set x-axis and y-axis label
+        if x_label is not None:
+            self.axes.set_xlabel(x_label, fontsize=20)
+        if y_label is not None:
+            self.axes.set_ylabel(y_label, fontsize=20)
+
+        # set/update legend
+        self._setupLegend()
+
+        # Register
+        line_key = self._lineIndex
+        if len(r) == 1:
+            self._lineDict[line_key] = r[0]
+            self._lineIndex += 1
+        else:
+            print "Impoooooooooooooooosible!  Return from plot is a %d-tuple. " % (len(r))
+
+        # Flush/commit
+        self.draw()
+
+        return line_key
+
+    def add_1d_plot_right(self, x, y, color=None, label="", x_label=None, ylabel=None, marker=None, linestyle=None,
+                          linewidth=1):
+        """ Add a line (1-d plot) at right axis
+        """
+        if self.axes2 is None:
+            self.axes2 = self.axes.twinx()
+            # print self.par1, type(self.par1)
+
+        # Hold previous data
+        self.axes2.hold(True)
+
+        # Default
+        if color is None:
+            color = (0, 1, 0, 1)
+        if marker is None:
+            marker = 'o'
+        if linestyle is None:
+            linestyle = '-'
+
+        # Special default
+        if len(label) == 0:
+            label = 'right'
+            color = 'red'
+
+        # color must be RGBA (4-tuple)
+        r = self.axes2.plot(x, y, color=color, marker=marker, linestyle=linestyle,
+                            label=label, linewidth=linewidth)
+        # return: list of matplotlib.lines.Line2D object
+
+        self.axes2.set_aspect('auto')
+
+        # set x-axis and y-axis label
+        if x_label is not None:
+            self.axes2.set_xlabel(x_label, fontsize=20)
+        if ylabel is not None:
+            self.axes2.set_ylabel(ylabel, fontsize=20)
+
+        # set/update legend
+        self._setupLegend()
+
+        # Register
+        line_key = -1
+        if len(r) == 1:
+            line_key = self._lineIndex
+            self._lineDict[line_key] = r[0]
+            self._lineIndex += 1
+        else:
+            print "Impoooooooooooooooosible!"
+
+        # Flush/commit
+        self.draw()
+
+        return line_key
+
+
+    def addPlot2D(self, array2d, xmin, xmax, ymin, ymax, holdprev, yticklabels=None):
+        """ Add a 2D plot
+
+        Arguments:
+         - yticklabels :: list of string for y ticks
+        """
+        # Release the current image
+        self.axes.hold(holdprev)
+
+        # Do plot
+        # y ticks will be shown on line 1, 4, 23, 24 and 30
+        # yticks = [1, 4, 23, 24, 30]
+        # self.axes.set_yticks(yticks)
+
+        # show image
+        imgplot = self.axes.imshow(array2d, extent=[xmin,xmax,ymin,ymax], interpolation='none')
+        # set y ticks as an option:
+        if yticklabels is not None:
+            # it will always label the first N ticks even image is zoomed in
+            print "--------> [FixMe]: Set up the Y-axis ticks is erroreous"
+            #self.axes.set_yticklabels(yticklabels)
+
+        # explicitly set aspect ratio of the image
+        self.axes.set_aspect('auto')
+
+        # Set color bar.  plt.colorbar() does not work!
+        if self._colorBar is None:
+            # set color map type
+            imgplot.set_cmap('spectral')
+            self._colorBar = self.fig.colorbar(imgplot)
+        else:
+            self._colorBar.update_bruteforce(imgplot)
+
+        # Flush...
+        self._flush()
+
+        return
+
+    def addImage(self, imagefilename):
+        """ Add an image by file
+        """
+        #import matplotlib.image as mpimg
+
+        # set aspect to auto mode
+        self.axes.set_aspect('auto')
+
+        img = matplotlib.image.imread(str(imagefilename))
+        # lum_img = img[:,:,0]
+        # FUTURE : refactor for image size, interpolation and origin
+        imgplot = self.axes.imshow(img, extent=[0, 1000, 800, 0], interpolation='none', origin='lower')
+
+        # Set color bar.  plt.colorbar() does not work!
+        if self._colorBar is None:
+            # set color map type
+            imgplot.set_cmap('spectral')
+            self._colorBar = self.fig.colorbar(imgplot)
+        else:
+            self._colorBar.update_bruteforce(imgplot)
+
+        self._flush()
+
+        return
+
+    def clear_all_1d_plots(self):
+        """ Remove all lines from the canvas
+        """
+        for ikey in self._lineDict.keys():
+            plot = self._lineDict[ikey]
+            if plot is None:
+                continue
+            if isinstance(plot, tuple) is False:
+                try:
+                    self.axes.lines.remove(plot)
+                except ValueError as e:
+                    print "[Error] Plot %s is not in axes.lines which has %d lines. Error mesage: %s" % (
+                        str(plot), len(self.axes.lines), str(e))
+                self._lineDict[ikey] = None
+            else:
+                # error bar
+                plot[0].remove()
+                for line in plot[1]:
+                    line.remove()
+                for line in plot[2]:
+                    line.remove()
+                self._lineDict[ikey] = None
+            # ENDIF(plot)
+        # ENDFOR
+
+        self._setupLegend()
+
+        self.draw()
+
+        return
+
+    def clear_canvas(self):
+        """ Clear data including lines and image from canvas
+        """
+        # clear the image for next operation
+        self.axes.hold(False)
+
+        # Clear all lines
+        self.clear_all_1d_plots()
+
+        # clear image
+        self.axes.cla()
+        # Try to clear the color bar
+        if len(self.fig.axes) > 1:
+            self.fig.delaxes(self.fig.axes[1])
+            self._colorBar = None
+            # This clears the space claimed by color bar but destroys sub_plot too.
+            self.fig.clear()
+            # Re-create subplot
+            self.axes = self.fig.add_subplot(111)
+
+        # flush/commit
+        self._flush()
+
+        return
+
+
+    def getLastPlotIndexKey(self):
+        """ Get the index/key of the last added line
+        """
+        return self._lineIndex-1
+
+
+    def getPlot(self):
+        """ reture figure's axes to expose the matplotlib figure to PyQt client
+        """
+        return self.axes
+
+    def getXLimit(self):
+        """ Get limit of Y-axis
+        """
+        return self.axes.get_xlim()
+
+    def getYLimit(self):
+        """ Get limit of Y-axis
+        """
+        return self.axes.get_ylim()
+
+    def setXYLimit(self, xmin, xmax, ymin, ymax):
+        """
+        """
+        # for X
+        xlims = self.axes.get_xlim()
+        xlims = list(xlims)
+        if xmin is not None:
+            xlims[0] = xmin
+        if xmax is not None:
+            xlims[1] = xmax
+        self.axes.set_xlim(xlims)
+
+        # for Y
+        ylims = self.axes.get_ylim()
+        ylims = list(ylims)
+        if ymin is not None:
+            ylims[0] = ymin
+        if ymax is not None:
+            ylims[1] = ymax
+        self.axes.set_ylim(ylims)
+
+        # try draw
+        self.draw()
+
+        return
+
+    def remove_plot_1d(self, plot_key):
+        """ Remove the line with its index as key
+        :param plot_key:
+        :return:
+        """
+        # self._lineDict[ikey].remove()
+        print 'Remove line... ',
+
+        # Get all lines in list
+        lines = self.axes.lines
+        assert isinstance(lines, list)
+
+        print 'Number of lines = %d, List: %s' % (len(lines), str(lines))
+        print 'Line to remove: key = %s, Line Dict has key = %s' % (str(plot_key), str(self._lineDict.has_key(plot_key)))
+
+        if plot_key in self._lineDict:
+            self.axes.lines.remove(self._lineDict[plot_key])
+            self._lineDict[plot_key] = None
+        else:
+            raise RuntimeError('Line with ID %s is not recorded.' % plot_key)
+
+        # Draw
+        self.draw()
+
+        return
+
+    def updateLine(self, ikey, vecx, vecy, linestyle=None, linecolor=None, marker=None, markercolor=None):
+        """
+        """
+        line = self._lineDict[ikey]
+
+        if vecx is not None and vecy is not None:
+            line.set_xdata(vecx)
+            line.set_ydata(vecy)
+
+        if linecolor is not None:
+            line.set_color(linecolor)
+
+        if linestyle is not None:
+            line.set_linestyle(linestyle)
+
+        if marker is not None:
+            line.set_marker(marker)
+
+        if markercolor is not None:
+            line.set_markerfacecolor(markercolor)
+
+        oldlabel = line.get_label()
+        line.set_label(oldlabel)
+
+        self.axes.legend()
+
+        # commit
+        self.draw()
+
+        return
+
+    def getLineStyleList(self):
+        """
+        """
+        return MplLineStyles
+
+
+    def getLineMarkerList(self):
+        """
+        """
+        return MplLineMarkers
+
+    def getLineBasicColorList(self):
+        """
+        """
+        return MplBasicColors
+
+    def getDefaultColorMarkerComboList(self):
+        """ Get a list of line/marker color and marker style combination
+        as default to add more and more line to plot
+        """
+        combolist = []
+        nummarkers = len(MplLineMarkers)
+        numcolors = len(MplBasicColors)
+
+        for i in xrange(nummarkers):
+            marker = MplLineMarkers[i]
+            for j in xrange(numcolors):
+                color = MplBasicColors[j]
+                combolist.append( (marker, color) )
+            # ENDFOR (j)
+        # ENDFOR(i)
+
+        return combolist
+
+    def _flush(self):
+        """ A dirty hack to flush the image
+        """
+        w, h = self.get_width_height()
+        self.resize(w+1,h)
+        self.resize(w,h)
+
+        return
+
+    def _setupLegend(self, location='best'):
+        """ Set up legend
+        self.axes.legend()
+        Handler is a Line2D object. Lable maps to the line object
+        """
+        loclist = [
+            "best",
+            "upper right",
+            "upper left",
+            "lower left",
+            "lower right",
+            "right",
+            "center left",
+            "center right",
+            "lower center",
+            "upper center",
+            "center"]
+
+        # Check legend location valid or not
+        if location not in loclist:
+            location = 'best'
+
+        handles, labels = self.axes.get_legend_handles_labels()
+        self.axes.legend(handles, labels, loc=location)
+        # print handles
+        # print labels
+        #self.axes.legend(self._myLegendHandlers, self._myLegentLabels)
+
+        return
+
+# END-OF-CLASS (MplGraphicsView)
+
+
+class MyNavigationToolbar(NavigationToolbar2):
+    """ A customized navigation tool bar attached to canvas
+    Note:
+    * home, left, right: will not disable zoom/pan mode
+    * zoom and pan: will turn on/off both's mode
+
+    Other methods
+    * drag_pan(self, event): event handling method for dragging canvas in pan-mode
+    """
+    NAVIGATION_MODE_NONE = 0
+    NAVIGATION_MODE_PAN = 1
+    NAVIGATION_MODE_ZOOM = 2
+
+    def __init__(self, parent, canvas):
+        """ Initialization
+        """
+        NavigationToolbar2.__init__(self, canvas, canvas)
+
+        self._myParent = parent
+        self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE
+
+        return
+
+    def get_mode(self):
+        """
+        :return: integer as none/pan/zoom mode
+        """
+        return self._navigationMode
+
+    # Overriding base's methods
+    def draw(self):
+        """
+        Canvas is drawn called by pan(), zoom()
+        :return:
+        """
+        NavigationToolbar2.draw(self)
+
+        self._myParent.evt_view_updated()
+
+        return
+
+    def pan(self, *args):
+        """
+
+        :param args:
+        :return:
+        """
+        NavigationToolbar2.pan(self, args)
+
+        if self._navigationMode == MyNavigationToolbar.NAVIGATION_MODE_PAN:
+            # out of pan mode
+            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE
+        else:
+            # into pan mode
+            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_PAN
+
+        return
+
+    def zoom(self, *args):
+        """
+        Turn on/off zoom (zoom button)
+        :param args:
+        :return:
+        """
+        NavigationToolbar2.zoom(self, args)
+
+        if self._navigationMode == MyNavigationToolbar.NAVIGATION_MODE_ZOOM:
+            # out of zoom mode
+            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE
+        else:
+            # into zoom mode
+            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_ZOOM
+
+        return
+
+    def _update_view(self):
+        """
+        view update called by home(), back() and forward()
+        :return:
+        """
+        NavigationToolbar2._update_view(self)
+
+        self._myParent.evt_view_updated()
+
+        return
+
-- 
GitLab