diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/multi_color_dot_dialog.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/multi_color_dot_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..65a5cecaef2ef16ac7eedc95fb291fdec288f625
--- /dev/null
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/multi_color_dot_dialog.py
@@ -0,0 +1,488 @@
+import sys
+import platform
+import os
+
+from typing import List, Dict
+
+from PySide2 import QtWidgets, QtCore, QtGui
+from PySide2.QtWidgets import QWidget
+
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog.edit_value_color_dialog_super_class import \
+    EditValueColorDialog, display_color
+
+
+TOTAL = 7
+
+
+class BoundValidator(QtGui.QValidator):
+    """
+    Validator that allow float value and empty string
+    Value '-'  to allow typing negative float. If user type '-' only, it will
+        be checked when editing is finished.
+    """
+    def validate(self, input, pos):
+        if input in ['', '-']:
+            return QtGui.QValidator.Acceptable
+        try:
+            input = float(input)
+        except ValueError:
+            return QtGui.QValidator.Invalid
+        if -20 <= input <= 20:
+            return QtGui.QValidator.Acceptable
+        else:
+            return QtGui.QValidator.Invalid
+
+
+class MultiColorDotDialog(EditValueColorDialog):
+    def __init__(
+            self, parent: QWidget, value_color_str: str, upper_equal: bool):
+        """
+        Dialog to edit color for Multi-color Dot Plot
+
+        :param parent: the parent widget
+        :param value_color_str: string for value color to be saved in DB
+        :param upper_equal: flag to know if equal is on upper bound
+            or lower_bound
+        """
+        self.upper_equal = upper_equal
+
+        # list of widgets to display lower bound which is read only of the
+        # value range of rows
+        self.lower_bound_lnedits: List[QtWidgets.QLineEdit] = []
+        # list of widgets to display lower bound of the value range of rows
+        self.higher_bound_lnedits: List[QtWidgets.QLineEdit] = []
+        # List of buttons that allow user to add/edit color
+        self.select_color_btns: List[QtWidgets.QPushButton] = []
+        # list of labels to display color of the data points defined by
+        # the value range of rows
+        self.color_labels: List[QtWidgets.QLabel] = []
+
+        # After a higher bound widget is edited and enter is hitted, button's
+        # clicked signal will be called before higher_bound_editting_finished
+        # is reached. The following flags are to change that order.
+        self.changed_rowid = None
+        self.select_color_btn_clicked = False
+        self.save_colors_btn_clicked = False
+
+        # Check box for plotting data point less than all the rest
+        self.include_less_than_chkbox = QtWidgets.QCheckBox('Include')
+
+        # Check box for including data point greater than all the rest
+        self.include_greater_than_chkbox = QtWidgets.QCheckBox('Include')
+
+        # Keep track of value by rowid
+        self.rowid_value_dict: Dict[int, float] = {}
+
+        super(MultiColorDotDialog, self).__init__(parent, value_color_str)
+        self.setWindowTitle("Edit Multi Color Dot Plotting")
+
+        # set focus policy to not be clicked by hitting enter in higher bound
+        self.cancel_btn.setFocusPolicy(QtCore.Qt.NoFocus)
+        self.save_colors_btn.setFocusPolicy(QtCore.Qt.NoFocus)
+
+        self.set_value_rowid_dict()
+
+    def setup_ui(self) -> None:
+
+        for i in range(TOTAL):
+            if i == 0:
+                self.main_layout.addWidget(
+                    self.include_less_than_chkbox, 0, 0, 1, 1)
+                self.include_less_than_chkbox.setChecked(True)
+            if i == TOTAL - 1:
+                self.main_layout.addWidget(
+                    self.include_greater_than_chkbox, TOTAL - 1, 0, 1, 1)
+            (lower_bound_lnedit, higher_bound_lnedit,
+             select_color_btn, color_label) = self.add_row(i)
+            self.lower_bound_lnedits.append(lower_bound_lnedit)
+            self.higher_bound_lnedits.append(higher_bound_lnedit)
+            self.select_color_btns.append(select_color_btn)
+            self.color_labels.append(color_label)
+            self.set_color_enabled(i, False)
+
+        self.setup_complete_buttons(TOTAL)
+
+    def connect_signals(self) -> None:
+        self.include_greater_than_chkbox.clicked.connect(
+            lambda: self.on_include(TOTAL - 1,
+                                    self.include_greater_than_chkbox))
+        self.include_less_than_chkbox.clicked.connect(
+            lambda: self.on_include(0, self.include_less_than_chkbox))
+
+        for i in range(TOTAL):
+            self.higher_bound_lnedits[i].textChanged.connect(
+                lambda arg, idx=i: setattr(self, 'changed_rowid', idx))
+            self.higher_bound_lnedits[i].editingFinished.connect(
+                lambda idx=i: self.on_higher_bound_editing_finished(idx))
+            # some how pass False to on_select_color, have to handle this
+            # in add_row() instead
+            # self.select_color_btns[i].clicked.connect(
+            #     lambda idx=i: self.on_select_color(idx))
+
+        super().connect_signals()
+
+    def on_higher_bound_editing_finished(self, rowid: int):
+        """
+        Check value entered for higher bound:
+            + If the last row is empty string, that value will be eliminated
+                from condition.
+            + If other row is empty string, give error message
+            + If value not greater than previous row and less than latter row,
+                give error message. -20 and 20 are given to previous or latter
+                row if not exist b/c they are out of range of bound [-10, 10]
+        Call set_value_rowid_dict to keep track of value by rowid.
+        """
+        if self.changed_rowid is None:
+            # When the function isn't called from user's changing text on
+            # a higher_bound_lnedit, this function will be ignored
+            return
+
+        if len(self.rowid_value_dict) < self.changed_rowid < TOTAL - 1:
+            # When user edit the row that skips some row from the last row
+            # the current higher_bound_lnedit will be cleared
+            self.higher_bound_lnedits[self.changed_rowid].setText('')
+            self.changed_rowid = None
+            return
+        self.changed_rowid = None
+
+        prev_higher_bound = (
+            float(self.higher_bound_lnedits[rowid - 1].text())
+            if rowid > 0 else -20)
+
+        try:
+            curr_higher_bound = float(self.higher_bound_lnedits[rowid].text())
+        except ValueError:
+            # When the current higher_bound_lnedit is cleared.
+            if rowid < len(self.rowid_value_dict) - 1:
+                # If the cleared one isn't the last one, it shouldn't be
+                # allowed. A warning should be given.
+                msg = "Higher bound must be a number"
+                QtWidgets.QMessageBox.information(self, "Error", msg)
+                self.higher_bound_lnedits[rowid].setText(
+                    str(self.rowid_value_dict[rowid]))
+            else:
+                # If the cleared one is the last one, the lower_bound_lnedit
+                # of the next row will be cleared too.
+                self.set_color_enabled(rowid, False)
+                self.lower_bound_lnedits[rowid + 1].setText('')
+                self.set_value_rowid_dict()
+            self.select_color_btn_clicked = False
+            self.save_colors_btn_clicked = False
+            return
+        if rowid >= len(self.rowid_value_dict) - 1:
+            # When the current higher_bound_lnedits is on the last row
+            # set the limit 20 to be the next_higher_bound
+            next_higher_bound = 20
+        else:
+            next_higher_bound = float(
+                self.higher_bound_lnedits[rowid + 1].text())
+
+        if (curr_higher_bound <= prev_higher_bound
+                or curr_higher_bound >= next_higher_bound):
+            # If value enter to the current higher_bound_lnedit make it out
+            # of increasing sorting, a warning will be given, and the
+            # widget will be set to the original value.
+            cond = (f"{prev_higher_bound} < "
+                    if prev_higher_bound != -20 else '')
+            cond += (f"d < {next_higher_bound}"
+                     if next_higher_bound != 20 else 'd')
+            msg = f"Value entered must be: {cond}"
+            QtWidgets.QMessageBox.information(self, "Error", msg)
+            try:
+                self.higher_bound_lnedits[rowid].setText(
+                    str(self.rowid_value_dict[rowid]))
+            except KeyError:
+                # If the current row is the last one, there's no original value
+                # So the widget will be cleared.
+                self.higher_bound_lnedits[rowid].setText('')
+            self.select_color_btn_clicked = False
+            self.save_colors_btn_clicked = False
+            return
+        if ((rowid == 0 and self.include_less_than_chkbox.isChecked())
+                or rowid != 0):
+            # Enable button Select Color unless the row is the first row but
+            # Include checkbox isn't checked
+            self.set_color_enabled(rowid, True)
+        self.lower_bound_lnedits[rowid + 1].setText(str(curr_higher_bound))
+        self.set_value_rowid_dict()
+        if self.save_colors_btn_clicked:
+            self.on_save_color()
+        if self.select_color_btn_clicked:
+            self.on_select_color(rowid)
+
+    def set_value_rowid_dict(self):
+        """
+        Update rowid_value_dict to the current higher bound
+        Update lower bound of the last row which is for greater than all the
+            rest to be the max of all higher bound
+        """
+        self.rowid_value_dict = {i: float(self.higher_bound_lnedits[i].text())
+                                 for i in range(TOTAL - 1)
+                                 if self.higher_bound_lnedits[i].text() != ''}
+        last_row_lnedit = (self.lower_bound_lnedits[TOTAL - 1]
+                           if self.upper_equal
+                           else self.higher_bound_lnedits[TOTAL - 1])
+        if len(self.rowid_value_dict) == 0:
+            last_row_lnedit.clear()
+        else:
+            last_row_lnedit.setText(str(max(self.rowid_value_dict.values())))
+
+    def set_color_enabled(self, rowid: int, enabled: bool):
+        """
+        Enable color: allow to edit and display color
+        Disable color: disallow to edit and hide color
+        """
+        self.select_color_btns[rowid].setEnabled(enabled)
+        self.color_labels[rowid].setHidden(not enabled)
+
+    def on_select_color(self, rowid: int):
+        """
+        When clicking on Select Color button, Color Picker will pop up with
+            the default color is color_label's color.
+        User will select a color then save to update the selected color to
+            the color_label.
+        """
+        if self.changed_rowid is not None:
+            self.select_color_btn_clicked = True
+            self.on_higher_bound_editing_finished(self.changed_rowid)
+            return
+        self.select_color_btn_clicked = False
+        super().on_select_color(self.color_labels[rowid])
+
+    def on_include(self, rowid: int, chkbox: QtWidgets.QCheckBox):
+        """
+        When include_less_than_chkbox/include_greater_than_chkbox is clicked
+            decide to allow user select color based on the status of the
+            check box.
+        """
+        self.set_color_enabled(rowid, chkbox.isChecked())
+        # to be able to click on buttons after include checkbox is checked
+        self.changed_rowid = None
+
+    def set_row(self, vc_idx: int, value: float, color: str):
+        """
+        Add values to widgets in a row
+            + row 0: consider uncheck include checkbox if color='not plot' and
+                check otherwise.
+            + row TOTAL-1: check include checkbox and set value for lower bound
+            + all row other than TOTAL-1, set value for higher bound and
+                next row's lower bound, enable and display color
+        """
+        if vc_idx == 0:
+            if color == 'not plot':
+                self.include_less_than_chkbox.setChecked(False)
+            else:
+                self.include_less_than_chkbox.setChecked(True)
+        if vc_idx < TOTAL - 1:
+            self.higher_bound_lnedits[vc_idx].setText(str(value))
+            self.lower_bound_lnedits[vc_idx + 1].setText(str(value))
+        else:
+            self.include_greater_than_chkbox.setChecked(True)
+            if self.upper_equal:
+                self.lower_bound_lnedits[TOTAL - 1].setText(str(value))
+            else:
+                self.higher_bound_lnedits[TOTAL - 1].setText(str(value))
+        if color == 'not plot':
+            self.set_color_enabled(vc_idx, False)
+        else:
+            self.set_color_enabled(vc_idx, True)
+            display_color(self.color_labels[vc_idx], color)
+
+    def on_save_color(self):
+        """
+        Create value_color_str from GUI's info and close the GUI.
+            + Skip row that has no color
+            + color = color_label's color name
+            + if include_less_than_chkbox is not checked, 'not_plot' will be
+                set for the first color
+            + Format for value_color of all row other than TOTAL - 1th:
+                <=value:color
+            + If include_greater_than_chkbox is checked, format will be:
+                value<:color
+        """
+        if self.changed_rowid and self.changed_rowid < TOTAL - 1:
+            print("on_save_color")
+            self.save_colors_btn_clicked = True
+            self.on_higher_bound_editing_finished(self.changed_rowid)
+            return
+        self.save_colors_btn_clicked = False
+        value_color_list = []
+        for i in range(TOTAL - 1):
+            if self.color_labels[i].isHidden() and i != 0:
+                continue
+            value = self.higher_bound_lnedits[i].text()
+            color = self.color_labels[i].palette(
+            ).window().color().name()
+            if i == 0 and not self.include_less_than_chkbox.isChecked():
+                color = 'not plot'
+            operator = '<=' if self.upper_equal else '<'
+            value_color_list.append(f"{operator}{value}:{color}")
+        if self.include_greater_than_chkbox.isChecked():
+
+            color = self.color_labels[TOTAL - 1].palette().window(
+                ).color().name()
+            if self.upper_equal:
+                val = f"{self.lower_bound_lnedits[TOTAL - 1].text()}"
+                value_color_list.append(f"{val}<:{color}")
+            else:
+                val = f"{self.higher_bound_lnedits[TOTAL - 1].text()}"
+                value_color_list.append(f"={val}:{color}")
+
+        self.value_color_str = '|'.join(value_color_list)
+        self.close()
+
+
+class MultiColorDotLowerEqualDialog(MultiColorDotDialog):
+    def __init__(self, parent, value_color_str):
+        super(MultiColorDotLowerEqualDialog, self).__init__(
+            parent, value_color_str, upper_equal=False)
+
+    def add_row(self, rowid):
+        """
+        Adding a row including Lower bound line dit, comparing operator label,
+            higher bound line edit, select color button, display color label
+        """
+        lower_bound_lnedit = QtWidgets.QLineEdit()
+        self.main_layout.addWidget(lower_bound_lnedit, rowid, 1, 1, 1)
+        lower_bound_lnedit.setReadOnly(True)
+        if rowid == 0:
+            lower_bound_lnedit.setHidden(True)
+            comp_text = "      d <"
+        elif rowid < TOTAL - 1:
+            lower_bound_lnedit.setEnabled(False)
+            comp_text = "<= d <"
+        else:
+            lower_bound_lnedit.setHidden(True)
+            comp_text = "      d ="
+        self.main_layout.addWidget(QtWidgets.QLabel(comp_text), rowid, 2, 1, 1)
+
+        higher_bound_lnedit = QtWidgets.QLineEdit()
+        validator = BoundValidator()
+        higher_bound_lnedit.setValidator(validator)
+        self.main_layout.addWidget(higher_bound_lnedit, rowid, 3, 1, 1)
+        if rowid == TOTAL - 1:
+            higher_bound_lnedit.setEnabled(False)
+
+        select_color_btn = QtWidgets.QPushButton("Select Color")
+        # set focus policy to not be clicked by hitting enter in higher bound
+        select_color_btn.setFocusPolicy(QtCore.Qt.NoFocus)
+        self.main_layout.addWidget(select_color_btn, rowid, 4, 1, 1)
+
+        color_label = QtWidgets.QLabel()
+        color_label.setFixedWidth(30)
+        color_label.setAutoFillBackground(True)
+        self.main_layout.addWidget(color_label, rowid, 5, 1, 1)
+
+        select_color_btn.clicked.connect(
+            lambda: self.on_select_color(rowid))
+        return (lower_bound_lnedit, higher_bound_lnedit,
+                select_color_btn, color_label)
+
+    def set_value(self):
+        """
+        Change the corresponding color_labels's color, higher/lower bound,
+            include check boxes according to value_color_str.
+        """
+        self.include_greater_than_chkbox.setChecked(False)
+        self.include_less_than_chkbox.setChecked(False)
+        if self.value_color_str == "":
+            return
+        vc_parts = self.value_color_str.split('|')
+        count = 0
+        for vc_str in vc_parts:
+            value, color = vc_str.split(':')
+            if value.startswith('<'):
+                # Ex: <1:#00FFFF
+                # Ex: <0:not plot  (can be on first value only)
+                value = value.replace('<', '')
+                self.set_row(count, float(value), color)
+                count += 1
+            else:
+                # Ex: =1:#FF00FF
+                value = value.replace('=', '')
+                self.set_row(TOTAL - 1, float(value), color)
+
+
+class MultiColorDotUpperEqualDialog(MultiColorDotDialog):
+    def __init__(self, parent, value_color_str):
+        super(MultiColorDotUpperEqualDialog, self).__init__(
+            parent, value_color_str, upper_equal=True)
+
+    def add_row(self, rowid):
+        """
+        Adding a row including Lower bound line dit, comparing operator label,
+            higher bound line edit, select color button, display color label
+        """
+        lower_bound_lnedit = QtWidgets.QLineEdit()
+        self.main_layout.addWidget(lower_bound_lnedit, rowid, 1, 1, 1)
+        lower_bound_lnedit.setReadOnly(True)
+        if rowid == 0:
+            lower_bound_lnedit.setHidden(True)
+            comp_text = "   d <="
+        elif rowid < TOTAL - 1:
+            lower_bound_lnedit.setEnabled(False)
+            comp_text = "< d <="
+        else:
+            lower_bound_lnedit.setReadOnly(True)
+            comp_text = "< d   "
+        self.main_layout.addWidget(QtWidgets.QLabel(comp_text), rowid, 2, 1, 1)
+
+        higher_bound_lnedit = QtWidgets.QLineEdit()
+        validator = BoundValidator()
+        higher_bound_lnedit.setValidator(validator)
+        self.main_layout.addWidget(higher_bound_lnedit, rowid, 3, 1, 1)
+        if rowid == TOTAL - 1:
+            higher_bound_lnedit.setHidden(True)
+
+        select_color_btn = QtWidgets.QPushButton("Select Color")
+        # set focus policy to not be clicked by hitting enter in higher bound
+        select_color_btn.setFocusPolicy(QtCore.Qt.NoFocus)
+        self.main_layout.addWidget(select_color_btn, rowid, 4, 1, 1)
+
+        color_label = QtWidgets.QLabel()
+        color_label.setFixedWidth(30)
+        color_label.setAutoFillBackground(True)
+        self.main_layout.addWidget(color_label, rowid, 5, 1, 1)
+
+        select_color_btn.clicked.connect(
+            lambda: self.on_select_color(rowid))
+        return (lower_bound_lnedit, higher_bound_lnedit,
+                select_color_btn, color_label)
+
+    def set_value(self):
+        """
+        Change the corresponding color_labels's color, higher/lower bound,
+            include check boxes according to value_color_str.
+        """
+        self.include_greater_than_chkbox.setChecked(False)
+        self.include_less_than_chkbox.setChecked(False)
+        if self.value_color_str == "":
+            return
+        vc_parts = self.value_color_str.split('|')
+        count = 0
+        for vc_str in vc_parts:
+            value, color = vc_str.split(':')
+            if value.startswith('<='):
+                # Ex: <=1:#00FFFF
+                # Ex: <=0:not plot  (can be on first value only)
+                value = value.replace('<=', '')
+                self.set_row(count, float(value), color)
+                count += 1
+            else:
+                # Ex: 1<:#FF00FF
+                value = value.replace('<', '')
+                self.set_row(TOTAL - 1, float(value), color)
+
+
+if __name__ == '__main__':
+    os_name, version, *_ = platform.platform().split('-')
+    if os_name == 'macOS':
+        os.environ['QT_MAC_WANTS_LAYER'] = '1'
+    app = QtWidgets.QApplication(sys.argv)
+    # test = MultiColorDotLowerEqualDialog(
+    #     None, '<3:#FF0000|<3.3:#FFFF00|=3.3:#00FF00')
+    test = MultiColorDotUpperEqualDialog(
+        None, '<=0:not plot|<=1:#FFFF00|<=2:#00FF00|2<:#FF00FF')
+    test.exec_()
+    sys.exit(app.exec_())