diff --git a/sohstationviewer/conf/constants.py b/sohstationviewer/conf/constants.py
index a9a3de9458abfef959b0d12824baa940332c414d..b5b96cb2b976b5048fba0952a94fd7de27ab7bb4 100644
--- a/sohstationviewer/conf/constants.py
+++ b/sohstationviewer/conf/constants.py
@@ -1,5 +1,10 @@
+import os
+from pathlib import Path
 from typing import Literal
 
+# The path to the package's root
+ROOT_PATH = Path(os.path.abspath(__file__)).parent.parent
+
 # The current version of SOHStationViewer
 SOFTWARE_VERSION = '2024.2.1.0'
 
diff --git a/sohstationviewer/conf/dbSettings.py b/sohstationviewer/conf/dbSettings.py
index 7de0d2566fe2668ea96ead051c25cdb8f32ca7f7..57b63c1697272ee7d68b892498bd57d7fd7075e9 100755
--- a/sohstationviewer/conf/dbSettings.py
+++ b/sohstationviewer/conf/dbSettings.py
@@ -16,33 +16,6 @@ dbConf = {
     'seisRE': re.compile(f'[{WF_1ST}][{WF_2ND}][{WF_3RD}]'),
     # key is last char of chan
     'seisLabel': {'1': 'NS', '2': 'EW', 'N': 'NS', 'E': 'EW', 'Z': 'V'},
-    # +0.2:Y
-    'multiColorDots': {
-        'pattern': re.compile('^\+?\-?[0-9]+\.?[0-9]?:[RYGMWC_]'),  # noqa: W605,E501
-        'instruction': (
-            "Colors include: RYGMCW\n"
-            "Ex: *:W  means everything with white color\n",
-            "Ex: -1:_|0:R|2.3:Y|+2.3:G  means:\n"
-            "   value <= -1  => not plot\n"
-            "   value <= 0   => plot with R color\n"
-            "   value <= 2.3 => plot with Y color\n"
-            "   value > 2.3  => plot with G color")},
-    'upDownDots': {
-        'pattern': re.compile("^[01]:[WRYGMC]$"),
-        'instruction': (
-            "Colors include: RYGMCW\n"
-            "Ex: 1:Y|0:R  means:\n"
-            "   value == 1 => plot above center line with Y color\n"
-            "   value == 0 => plot under center line with R color")},
-    'linesDots': {
-        'pattern': re.compile('^[LD]:[WRYGMC]$'),
-        'instruction': (
-            "Colors include: RYGMCW\n"
-            "Ex: L:G|D:W  means\n"
-            "   Lines are plotted with color G\n"
-            "   Dots are plotted with color W\n"
-            "If D is not defined, dots won't be displayed.\n"
-            "If L is not defined, lines will be plotted with color G")}
 }
 
 
diff --git a/sohstationviewer/controller/util.py b/sohstationviewer/controller/util.py
index 33693f35f9f97e34b736867444042279cf44ec5b..359ea5077a0f3cd09cdee802ad3f2ed018357735 100644
--- a/sohstationviewer/controller/util.py
+++ b/sohstationviewer/controller/util.py
@@ -171,12 +171,16 @@ def get_time_4(time_str: str, nearest_prior_epoch: float) -> float:
 
 def get_val(text: str) -> float:
     """
-    Get the value part of a string with non-number substring following.
-    :param text: value string including unit
+    Get the value part of a string. Examples:
+        - 6.5V -> 6.5
+        - <=2.2 -> 2.2
+        - <2 -> 2.0
+        - 6m -> 6.0
+    :param text: value string.
     :return: value part including +/-, remove str that follows
-        and remove '=' prefix
+        and remove '<' and '=' characters
     """
-    text = text.replace('=', '')
+    text = text.replace('=', '').replace('<', '')
     re_val = '^\+?\-?[0-9]+\.?[0-9]?'            # noqa: W605
     return float(re.search(re_val, text).group())
 
@@ -418,3 +422,43 @@ def get_valid_file_count(list_of_dir: Path) -> int:
             total += len([f for f in files
                          if validate_file(Path(path).joinpath(f), f)])
     return total
+
+
+def _format_time_item(
+        unit: str, value: Union[int, float], time_list: List[str]):
+    """
+    Append the format string of time item to time_list if not zero
+
+    :param unit: a time unit (day/hour/minute/second)
+    :param value: value of the time item
+    :param time_list: list of different formatted time item
+    """
+    if value == 0:
+        return
+    else:
+        formatted = f"{value} {unit}"
+        if value > 1:
+            formatted += 's'
+    time_list.append(formatted)
+
+
+def get_formatted_time_delta(time_delta: float) -> str:
+    """
+    Convert time_delta in seconds into formatted string of
+    (days, hours, minutes, seconds)
+    :param time_delta: length of time delta in seconds
+    :return formatted string of time delta
+    """
+    time_list = []
+    days = int(time_delta // 86400)
+    _format_time_item('day', days, time_list)
+    hours = int((time_delta - days * 86400) // 3600)
+    _format_time_item('hour', hours, time_list)
+    minutes = int((time_delta - days * 86400 - hours * 3600) // 60)
+    _format_time_item('minute', minutes, time_list)
+    seconds = time_delta - days * 86400 - hours * 3600 - minutes * 60
+    _format_time_item('second', seconds, time_list)
+    if not time_list:
+        return "0.0 seconds"
+    else:
+        return " ".join(time_list)
diff --git a/sohstationviewer/database/soh.db b/sohstationviewer/database/soh.db
index c794630c77617836de66f71fafd9962f2b0219d3..60f5491015a4f48fda6b6901233354727586f55b 100755
Binary files a/sohstationviewer/database/soh.db and b/sohstationviewer/database/soh.db differ
diff --git a/sohstationviewer/model/general_data/data_structures.MD b/sohstationviewer/model/general_data/data_structures.MD
index 36a59d5fad70a9e67d582b3df0e0c6d976448404..524b3af6e5a362979929fcba9edce79c171f8f4d 100644
--- a/sohstationviewer/model/general_data/data_structures.MD
+++ b/sohstationviewer/model/general_data/data_structures.MD
@@ -25,8 +25,9 @@ Note: log_data for RT130's dataset has only one channel: SOH
         'data' (np.array): data that has been trimmed and down-sampled for plotting
         'chan_db_info' (dict): the plotting parameters got from database
             for this channel - dict,
-        ax: axes to draw the channel in PlottingWidget
-        ax_wf (matplotlib.axes.Axes): axes to draw the channel in WaveformWidget
+        'ax': axes to draw the channel in PlottingWidget
+        'ax_wf' (matplotlib.axes.Axes): axes to draw the channel in WaveformWidget
+        'visible': flag to show or hide channel
     }
 }
 
diff --git a/sohstationviewer/model/general_data/general_data.py b/sohstationviewer/model/general_data/general_data.py
index 966d40e0080ea0853a92ba163929e719595b3356..096c745dc793dec904052a32c74440b59c996265 100644
--- a/sohstationviewer/model/general_data/general_data.py
+++ b/sohstationviewer/model/general_data/general_data.py
@@ -370,6 +370,8 @@ class GeneralData():
         """
         for data_set_id in self.data_set_ids:
             self.gaps[data_set_id] = []
+            if self.gap_minimum is None:
+                continue
             retrieve_gaps_from_data_dict(data_set_id,
                                          self.soh_data, self.gaps)
             retrieve_gaps_from_data_dict(data_set_id,
diff --git a/sohstationviewer/model/mseed_data/mseed_reader.py b/sohstationviewer/model/mseed_data/mseed_reader.py
index a33569fea6e83888e9811fb0118d60db76c50321..ac66513a4d73f4a4eb8cf6f5f957447c652f9dca 100644
--- a/sohstationviewer/model/mseed_data/mseed_reader.py
+++ b/sohstationviewer/model/mseed_data/mseed_reader.py
@@ -187,14 +187,29 @@ class MSeedReader:
 
     def _append_trace(self, channel: Dict,
                       data_points: Tuple[Real, Real],
-                      times: Tuple[Real, Real]):
+                      times: Tuple[Real, Real],
+                      one_sample_apart: bool):
         """
         Appending data_point to the latest trace of channel['tracesInfo']
 
+        If the added trace is one sample apart from the previous one, the last
+        point before adding new trace will be removed.
+
         :param channel: dict of channel's info
         :param data_points: data points extracted from the record frame
         :param times: time data of the data point
+        :param one_sample_apart: flag to tell if the added trace is one sample
+            apart from the previous one.
         """
+        if one_sample_apart:
+            # Remove last point of the previous trace if it is only one sample
+            # apart from beginning of the next one to not showing 2 points that
+            # is very close together.
+            channel['tracesInfo'][-1]['data'] = channel['tracesInfo'][-1][
+                                                    'data'][:-1]
+            channel['tracesInfo'][-1]['times'] = channel['tracesInfo'][-1][
+                                                    'times'][:-1]
+
         channel['tracesInfo'][-1]['data'].extend(data_points)
         channel['tracesInfo'][-1]['times'].extend(times)
 
@@ -203,9 +218,12 @@ class MSeedReader:
                       times: Tuple[Real, Real]) -> None:
         """
         Add new channel to the passed station and append data_point to the
-            channel if there's no gap/overlap or start a new trace of data
-            when there's a gap.
-        If gap/overlap > gap_minimum, add to gaps list.
+        channel if there's no gap/overlap or start a new trace of data
+        when there's a gap.
+
+        If gap/overlap > gap_minimum, add to gaps list but gap won't be added
+        if the added trace is one sample apart from the previous one to keep
+        the view sample rate for the channel consistent.
 
         :param station: dict of chan by id of a station
         :param metadata: metadata of the record the data points are extracted
@@ -230,24 +248,33 @@ class MSeedReader:
                     'endTmEpoch': meta.end_time,
                     'data': list(data_points),
                     'times': list(times)
-                }]
+                }],
+                'visible': True
             }
         else:
             channel = station[chan_id]
             record_start_time = meta.start_time
             previous_end_time = channel['endTmEpoch']
-            delta = abs(record_start_time - previous_end_time)
+            delta = record_start_time - previous_end_time
+            sample_interval = 1 / meta.sample_rate
             if channel['file_path'] != self.file_path:
                 # Start new trace for each file to reorder trace and
                 # combine traces again later
                 channel['file_path'] = self.file_path
                 self._add_new_trace(channel, meta, data_points, times)
             else:
-                if self.gap_minimum is not None and delta >= self.gap_minimum:
+                if (self.gap_minimum is not None and
+                        abs(delta) >= self.gap_minimum and
+                        delta != sample_interval):
                     gap = [previous_end_time, record_start_time]
                     channel['gaps'].append(gap)
                 # appending data
-                self._append_trace(channel, data_points, times)
+                if delta == sample_interval:
+                    one_sample_apart = True
+                else:
+                    one_sample_apart = False
+                self._append_trace(channel, data_points, times,
+                                   one_sample_apart)
 
             channel['tracesInfo'][-1]['endTmEpoch'] = meta.end_time
             # update channel's metadata
diff --git a/sohstationviewer/model/reftek_data/log_info.py b/sohstationviewer/model/reftek_data/log_info.py
index c2f1c40ddd919181969e97c3fd6fb4450313bd34..8b81ebfd1d9c50a1f27dbcc5bbe12d458fdcb1ef 100644
--- a/sohstationviewer/model/reftek_data/log_info.py
+++ b/sohstationviewer/model/reftek_data/log_info.py
@@ -496,6 +496,7 @@ class LogInfo():
                 'reftek': True,
                 'samplerate': 1,
                 'chanID': chan_id,
+                'visible': True,
                 'tracesInfo': [{
                     'unitID': self.unit_id,
                     'expNo': self.exp_no,
diff --git a/sohstationviewer/model/reftek_data/reftek_helper.py b/sohstationviewer/model/reftek_data/reftek_helper.py
index 69972c783ff8fc361ce7995cf74777ab62cc7d7e..13484c74ede41a6c5a55e2059b4637768debb6c3 100644
--- a/sohstationviewer/model/reftek_data/reftek_helper.py
+++ b/sohstationviewer/model/reftek_data/reftek_helper.py
@@ -50,7 +50,8 @@ def check_reftek_header(
         samplerate = trace.stats['sampling_rate']
         if chan_id not in cur_data_dict:
             cur_data_dict[chan_id] = {'tracesInfo': [],
-                                      'samplerate': samplerate}
+                                      'samplerate': samplerate,
+                                      'visible': True}
         if trace.stats.npts == 0:
             #  this trace isn't available to prevent bug when creating memmap
             #  with no data
diff --git a/sohstationviewer/view/channel_prefer_dialog.py b/sohstationviewer/view/channel_prefer_dialog.py
index f394b4a98c4d79ce317fd8ed9c60c798ea5e9f5c..7446238f6fe72561357d162616ec38effb462a0e 100755
--- a/sohstationviewer/view/channel_prefer_dialog.py
+++ b/sohstationviewer/view/channel_prefer_dialog.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, Union
+from typing import Dict, List, Union, Optional, Tuple
 from pathlib import Path
 
 from PySide6 import QtWidgets, QtCore
@@ -24,7 +24,8 @@ main window.
 """
 TOTAL_ROW = 20
 
-COL = {'sel': 0, 'name': 1, 'dataType': 2, 'IDs': 3, 'edit': 4, 'clr': 5}
+COL = {'sel': 0, 'name': 1, 'dataType': 2,
+       'preferredSOHs': 3, 'edit': 4, 'clr': 5}
 
 
 class InputDialog(QDialog):
@@ -81,7 +82,8 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         """
         avail_data_types: list of available data types in DB
         """
-        self.avail_data_types: List[str] = []
+        self.avail_data_types: List[str] = self.get_data_types(
+            include_default=False)
         """
         curr_row: current row
         """
@@ -241,7 +243,6 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         )
 
         self.soh_list_table_widget.setRowCount(TOTAL_ROW)
-        self.avail_data_types = self.get_data_types()
         for row_idx in range(TOTAL_ROW):
             self.add_row(row_idx)
         self.update_data_table_widget_items()
@@ -285,7 +286,8 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
             row_idx, COL['dataType'], data_type_combo_box)
 
         soh_list_item = QtWidgets.QTableWidgetItem()
-        self.soh_list_table_widget.setItem(row_idx, COL['IDs'], soh_list_item)
+        self.soh_list_table_widget.setItem(
+            row_idx, COL['preferredSOHs'], soh_list_item)
 
         edit_button = QtWidgets.QPushButton(self, text='EDIT')
         edit_button.clicked.connect(lambda arg: self.edit_soh_list(row_idx))
@@ -325,7 +327,7 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
             self.soh_list_table_widget.cellWidget(
                 count, COL['dataType']).setCurrentText(r['dataType'])
             self.soh_list_table_widget.item(
-                count, COL['IDs']).setText(r['IDs'])
+                count, COL['preferredSOHs']).setText(r['preferredSOHs'])
             if r['current'] == 1:
                 self.curr_sel_changed(count)
                 self.soh_list_table_widget.selectRow(count)
@@ -343,7 +345,7 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         data_type_combobox = self.soh_list_table_widget.cellWidget(
             row_idx, COL['dataType'])
         soh_list_item = self.soh_list_table_widget.item(
-            row_idx, COL['IDs'])
+            row_idx, COL['preferredSOHs'])
         clear_widget = self.soh_list_table_widget.cellWidget(
             row_idx, COL['clr'])
         return (soh_list_name_item,
@@ -366,7 +368,8 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
 
     @QtCore.Slot()
     def edit_soh_list(self, row_idx):
-        soh_list_item = self.soh_list_table_widget.item(row_idx, COL['IDs'])
+        soh_list_item = self.soh_list_table_widget.item(row_idx,
+                                                        COL['preferredSOHs'])
         edit_dialog = InputDialog(text=soh_list_item.text())
         if edit_dialog.exec():
             soh_list_item.setText(edit_dialog.get_input())
@@ -430,6 +433,7 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
     def add_db_channels(self):
         """
         Add channels from DB to preferred channel list.
+        A DB data_type is required in to retrieve its channels from DB
         """
         if not self.validate_row(check_data_type=True):
             return
@@ -550,9 +554,22 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         """
         if not self.validate_row():
             return
+        # call setFocus() to finish any QEditLine's editing or the editing not
+        # take affect
+        self.save_btn.setFocus()
+        sql_list = self.get_sql_list()
+
+        if sql_list is None:
+            return False
+        if len(sql_list) == 0:
+            self.parent.pref_soh_list = []
+            self.parent.pref_soh_list_name = ''
+            self.parent.pref_soh_list_data_type = 'Unknown'
+
         if self.changed:
-            msg = ("All IDs in the database will be overwritten with "
-                   "current IDs in the dialog.\nClick Cancel to stop updating "
+            msg = ("All Preferred SOH IDs in the database will be "
+                   "overwritten with current Preferred SOH IDs in the dialog."
+                   "\nClick Cancel to stop updating "
                    "database.")
             result = QtWidgets.QMessageBox.question(
                 self, "Confirmation", msg,
@@ -560,19 +577,10 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
                 QtWidgets.QMessageBox.StandardButton.Cancel)
             if result == QtWidgets.QMessageBox.StandardButton.Cancel:
                 return False
-        sql_list = []
-        for row_idx in range(TOTAL_ROW):
-            sql = self.create_save_row_sql(row_idx)
-            if sql is not None:
-                sql_list.append(sql)
-        if len(sql_list) == 0:
-            self.parent.pref_soh_list = []
-            self.parent.pref_soh_list_name = ''
-            self.parent.pref_soh_list_data_type = 'Unknown'
-
         ret = trunc_add_db('ChannelPrefer', sql_list)
         if ret is not True:
-            display_tracking_info(self.parent, ret, LogType.ERROR)
+            display_tracking_info(self.tracking_info_text_browser,
+                                  ret, LogType.ERROR)
         self.parent.pref_soh_list = [
             t.strip() for t in self.soh_list_item.text().split(',')]
         self.parent.pref_soh_list_name = self.soh_list_name_item.text().strip()
@@ -581,12 +589,73 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         self.changed = False
         return True
 
-    def create_save_row_sql(self, row_idx):
+    def get_sql_list(self) -> Optional[List[str]]:
+        """
+        Create list of sql to insert data to ChannelPrefer table in which:
+          + If there is error it will mark it to return but continue to report
+           all errors of all rows.
+          + Beside checking each row, primary key will be checked to make sure
+           the list_of_sql returned can be executed.
+
+        :return:
+          None if there are any error
+          Otherwise, list of sql
+        """
+        sql_list = []
+        display_id_by_primary_key_dict = {}
+        has_error = False
+        for row_idx in range(TOTAL_ROW):
+            result = self.create_save_row_sql(row_idx)
+            if result is None:
+                has_error = True
+                continue
+            else:
+                primary_key, display_id, sql = result
+                if sql == '':
+                    continue
+                sql_list.append(sql)
+                if primary_key not in display_id_by_primary_key_dict:
+                    display_id_by_primary_key_dict[primary_key] = []
+                display_id_by_primary_key_dict[primary_key].append(display_id)
+        if self.check_primary_key_duplicated(display_id_by_primary_key_dict):
+            return
+        if has_error:
+            return
+        return sql_list
+
+    def check_primary_key_duplicated(self, display_id_by_primary_key_dict) \
+            -> bool:
+        """
+        Check if there are any primary keys duplicated.
+        :param display_id_by_primary_key_dict: dictionary with primary key as
+            key and list of display row id for that primary key as value.
+        :return: True if duplicated, False otherwise
+        """
+        duplicated = False
+        duplicated_primary_keys = [
+            pk for pk in display_id_by_primary_key_dict
+            if len(display_id_by_primary_key_dict[pk]) > 1]
+        if duplicated_primary_keys:
+            duplicated = True
+            for pk in duplicated_primary_keys:
+                msg = (f"Name '{pk}' is duplicated in rows "
+                       f"{', '.join(display_id_by_primary_key_dict[pk])}.\n"
+                       f"Please change Name '{pk}' to make Name unique.")
+                QtWidgets.QMessageBox.information(self, "Missing info", msg)
+        return duplicated
+
+    def create_save_row_sql(self, row_idx: int) \
+            -> Optional[Tuple[str, str, str]]:
         """
         Read info from the row with index row_idx to create insert sql.
 
         :param row_idx: int - index of a row
-        :return: str - insert sql with the row's info
+        :return:
+            None: has error
+            Tuple of:
+              primary key of the row
+              display_id of the row
+              sql to insert row data to ChannelPrefer table
         """
         current = 1 if self.soh_list_table_widget.cellWidget(
             row_idx, COL['sel']).isChecked() else 0
@@ -594,16 +663,29 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
             row_idx, COL['name']).text()
         data_type = self.soh_list_table_widget.cellWidget(
             row_idx, COL['dataType']).currentText()
-        idx = self.soh_list_table_widget.item(
-            row_idx, COL['IDs']).text()
-        if idx.strip() == '':
+        preferred_sohs = self.soh_list_table_widget.item(
+            row_idx, COL['preferredSOHs']).text()
+        if preferred_sohs.strip() == '' and name.strip() == '':
+            return '', '', ''
+        display_id = row_idx + 1
+
+        if preferred_sohs.strip() == '' and name.strip() != '':
+            msg = f"Please add Preferred SOH IDs for row {display_id}."
+            QtWidgets.QMessageBox.information(self, "Missing info", msg)
+            return
+        if name.strip() == '' and preferred_sohs.strip() != '':
+            msg = f"Please add Name for row {display_id}."
+            QtWidgets.QMessageBox.information(self, "Missing info", msg)
             return
-        if name.strip() == '' and idx.strip() != '':
-            msg = f"Please add Name for row {row_idx}."
+        if preferred_sohs.strip() != '' and data_type == '':
+            msg = f"Please add a data type for row {display_id}."
             QtWidgets.QMessageBox.information(self, "Missing info", msg)
             return
-        return (f"INSERT INTO ChannelPrefer (name, IDs, dataType, current)"
-                f"VALUES ('{name}', '{idx}', '{data_type}', {current})")
+        sql = (f"INSERT INTO ChannelPrefer (name, preferredSOHs, dataType, "
+               f"current) VALUES "
+               f"('{name}', '{preferred_sohs}', '{data_type}', {current})")
+        primary_key = name
+        return primary_key, str(display_id), sql
 
     @QtCore.Slot()
     def save_add_to_main_and_close(self):
@@ -613,21 +695,31 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         """
         if not self.save():
             return
+        if self.parent.pref_soh_list_name == '':
+            msg = "Please select a row to add to Main Window."
+            QtWidgets.QMessageBox.information(self, "Missing info", msg)
+            return
         self.parent.curr_pref_soh_list_name_txtbox.setText(
             self.parent.pref_soh_list_name)
         self.parent.all_soh_chans_check_box.setChecked(False)
         self.close()
 
     @staticmethod
-    def get_data_types():
+    def get_data_types(include_default: bool = True):
         """
         Get list of data types from DB.
 
+        :param include_default: flag indicate if Default data type should be
+            included or not
         :return: [str, ] - list of data types
         """
         data_type_rows = execute_db(
             'SELECT * FROM DataTypes ORDER BY dataType ASC')
-        return [d[0] for d in data_type_rows]
+        if include_default:
+            return [d[0] for d in data_type_rows]
+        else:
+            return [d[0] for d in data_type_rows
+                    if d[0] != 'Default']
 
     @staticmethod
     def get_db_channels(data_type) -> List[str]:
@@ -652,6 +744,6 @@ class ChannelPreferDialog(OneWindowAtATimeDialog):
         :return id_rows: [dict,] - list of data for each row
         """
         id_rows = execute_db_dict(
-            "SELECT name, IDs, dataType, current FROM ChannelPrefer "
+            "SELECT name, preferredSOHs, dataType, current FROM ChannelPrefer "
             " ORDER BY name ASC")
         return id_rows
diff --git a/sohstationviewer/view/db_config/channel_dialog.py b/sohstationviewer/view/db_config/channel_dialog.py
index 2295442b5b7b422d5babfbe2fbaf1998e41c0d34..e4a969d9a256a1dacf9ac31b1ebf9233e206106a 100755
--- a/sohstationviewer/view/db_config/channel_dialog.py
+++ b/sohstationviewer/view/db_config/channel_dialog.py
@@ -64,17 +64,13 @@ class ChannelDialog(UiDBInfoDialog):
         :param fk: bool: True if there is a foreign constrain that prevents the
             row to be deleted
         """
-        self.add_widget(None, row_idx, 0)  # No.
-        self.add_widget(self.database_rows, row_idx, 1,
-                        foreign_key=fk)  # chanID
-        self.add_widget(self.database_rows, row_idx, 2)  # label
-        self.add_widget(self.database_rows, row_idx, 3,
-                        choices=self.param_choices)
-        self.add_widget(self.database_rows, row_idx, 4,
-                        field_name='convertFactor')
-        self.add_widget(self.database_rows, row_idx, 5)  # unit
-        self.add_widget(self.database_rows, row_idx, 6,
-                        range_values=[0, 5])  # fixPoint
+        self.add_row_number_button(row_idx)  # No.
+        self.add_widget(row_idx, 1, foreign_key=fk)  # chanID
+        self.add_widget(row_idx, 2)  # label
+        self.add_widget(row_idx, 3, choices=self.param_choices)
+        self.add_widget(row_idx, 4, field_name='convertFactor')
+        self.add_widget(row_idx, 5)  # unit
+        self.add_widget(row_idx, 6, range_values=[0, 5])  # fixPoint
         self.add_delete_button_to_row(row_idx, fk)
 
     def get_data_type_from_selector(self):
@@ -138,7 +134,7 @@ class ChannelDialog(UiDBInfoDialog):
             # case where the exit button is pressed.
             self.data_type_combo_box.setCurrentText(self.data_type)
 
-    def get_data_list(self):
+    def get_database_rows(self):
         """
         Get list of data to fill self.data_table_widgets' content
         """
diff --git a/sohstationviewer/view/db_config/data_type_dialog.py b/sohstationviewer/view/db_config/data_type_dialog.py
index 52a985787f27c3e703da561bb8c822e7ba45ae81..88fffac0af867f729bcdd506a9ff683afb0300c6 100755
--- a/sohstationviewer/view/db_config/data_type_dialog.py
+++ b/sohstationviewer/view/db_config/data_type_dialog.py
@@ -25,11 +25,11 @@ class DataTypeDialog(UiDBInfoDialog):
         :param fk: bool: True if there is a foreign constrain that prevents the
             row to be deleted
         """
-        self.add_widget(None, row_idx, 0)  # No.
-        self.add_widget(self.database_rows, row_idx, 1, foreign_key=fk)
+        self.add_row_number_button(row_idx)  # No.
+        self.add_widget(row_idx, 1, foreign_key=fk)
         self.add_delete_button_to_row(row_idx, fk)
 
-    def get_data_list(self):
+    def get_database_rows(self):
         """
         Get list of data to fill self.data_table_widgets' content
         """
diff --git a/sohstationviewer/view/db_config/db_config_dialog.py b/sohstationviewer/view/db_config/db_config_dialog.py
index 0454ab696f313ed76daefaea92f66d64f9932fab..64b88c7f8ac493a990468c1d26df8a15286c635f 100755
--- a/sohstationviewer/view/db_config/db_config_dialog.py
+++ b/sohstationviewer/view/db_config/db_config_dialog.py
@@ -3,10 +3,13 @@ from __future__ import annotations
 from typing import Dict, Optional, List, Tuple
 
 from PySide6 import QtWidgets, QtGui, QtCore
+from PySide6.QtCore import Signal
 from PySide6.QtGui import QCloseEvent
 from PySide6.QtWidgets import QMessageBox, QWidget
 
 from sohstationviewer.database.process_db import execute_db
+from sohstationviewer.view.db_config.value_color_helper.value_color_edit \
+    import ValueColorEdit
 from sohstationviewer.view.util.one_instance_at_a_time import \
     OneWindowAtATimeDialog
 
@@ -35,7 +38,12 @@ def set_widget_color(widget, changed=False, read_only=False):
     :param read_only: True: grey Text, blue background (priority)
     if both flags are False: black Text, white blackground
     """
-    palette = QtGui.QPalette()
+    if isinstance(widget, ValueColorEdit):
+        # ValueColorEdit handles its own styling, so we don't need to style
+        # it here.
+        return
+
+    palette = widget.palette()
 
     if read_only:
         # grey text
@@ -55,6 +63,11 @@ def set_widget_color(widget, changed=False, read_only=False):
         # red text
         palette.setColor(QtGui.QPalette.ColorRole.Text,
                          QtGui.QColor(255, 0, 0))
+        # The selected text in a QComboBox on Linux has ButtonText as its color
+        # role (probably because the QComboBox on Linux looks like one button,
+        # while the one on Mac looks like a TextEdit combined with a button).
+        palette.setColor(QtGui.QPalette.ColorRole.ButtonText,
+                         QtGui.QColor(255, 0, 0))
     else:
         try:
             if widget.isReadOnly():
@@ -124,7 +137,7 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
         """
         is_row_changed_array: a bit array that store whether a row is changed.
         """
-        self.is_row_changed_array: List[bool] = []
+        self.changes_array: List[List[bool]] = []
         """
         queued_row_delete_sqls: a dictionary of delete SQL statements that are
         executed when changes are saved to the database. Map a row id in the
@@ -182,50 +195,56 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
                                          0, 0, 1, 1)
             main_layout.addLayout(self.bottom_layout)
 
-    def add_widget(self, data_list, row_idx, col_idx, foreign_key=False,
-                   choices=None, range_values=None, field_name='',
-                   place_holder_text='') -> str:
-        """
-        Add a cell widget at a given row and column in the data_table_widget.
-
-        :param data_list: list of data to be display in the data_table_widget.
-            data_list is None when that column shows the row number.
-        :param row_idx: index of row
-        :param col_idx: index of column
-        :param foreign_key: True if there is a foreign constrain that prevents
-            the row to be deleted => set readonly of cell=True
-        :param choices: List of choices to add to a drop down list. If
-            it is available, QComboBox will be assigned for the widget.
-        :param range_values: list consists of min and max values. If
-            it is available, QSpinbox will be assigned for the widget
-        If no choices or range available, QLineEdit  will be assigned for the
-            widget.
-        :param field_name: name of the column in database. If field_name=
-            'convertFactor', add validator to limit precision to 6 decimal
-            points
-        :param place_holder_text: the text displayed in QTextEdit as a hint
-            for user
-        :return text: value of the current field in string
+    def add_row_number_button(self, row_idx: int) -> None:
+        """
+        Add a button that shows the row number to a row of the data table. The
+        button will be on the left of the row.
+        :param row_idx: the index of the row to insert the button
+        """
+        row_button = QtWidgets.QPushButton(str(row_idx))
+        set_widget_color(row_button)
+        row_button.setFixedWidth(40)
+        row_button.clicked.connect(
+            lambda: self.row_number_clicked(row_button))
+        self.data_table_widget.setCellWidget(row_idx, 0, row_button)
+
+    def create_widget(self, widget_content: str,
+                      choices: Optional[List] = None,
+                      range_values: Optional[List] = None,
+                      field_name: str = '', **kwargs
+                      ) -> Tuple[QWidget, Signal]:
+        """
+        Create a widget with the given content. The actual type of widget
+        created depends on the arguments passed in. By default, the created
+        widget will be a QLineEdit.
+
+        :param widget_content: the content of the created widget
+        :param choices: list of choices to add to a drop-down list. If this is
+            available, the created widget will be a QComboBox.
+        :param range_values: list consists of min and max values. If this is
+            available, the created widget will be a QSpinbox.
+        :param field_name: name of the column in database.
+            If field_name is 'convertFactor', a validator will be added to the
+            created widget, limiting the input to numbers in the range 0.0-5.0
+            with at most 6 decimal digits.
+            If field_name is 'description', the created widget will be a
+            QTextEdit.
+        :return:
+            - a widget whose type depends on the arguments passed in.
+            - the QT signal that is emitted when the content of the returned
+                widget is modified
         """
-        if data_list is None:
-            text = str(row_idx)  # row number
-        else:
-            text = (str(data_list[row_idx][col_idx - 1])
-                    if row_idx < len(data_list) else '')
-
-        if data_list is None:
-            widget = QtWidgets.QPushButton(text)
-        elif choices is None and range_values is None:
+        widget, change_signal = None, None
+        if choices is None and range_values is None:
             if field_name == 'description':
                 widget = QtWidgets.QTextEdit()
                 height = 12
-                for line in text.split('\n'):
+                for line in widget_content.split('\n'):
                     widget.append(line)
                     height += 16
                 widget.setFixedHeight(height)
             else:
-                widget = QtWidgets.QLineEdit(text)
-                widget.setPlaceholderText(place_holder_text)
+                widget = QtWidgets.QLineEdit(widget_content)
             if field_name == 'convertFactor':
                 # precision=6
                 validator = QtGui.QDoubleValidator(0.0, 5.0, 6)
@@ -233,46 +252,72 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
                     QtGui.QDoubleValidator.Notation.StandardNotation
                 )
                 widget.setValidator(validator)
-                if text == '':
+                if widget_content == '':
                     widget.setText('1')
+            change_signal = widget.textChanged
         elif choices:
             widget = QtWidgets.QComboBox()
             widget.addItems(choices)
-            widget.setCurrentText(text)
+            widget.setCurrentText(widget_content)
+            change_signal = widget.currentTextChanged
         elif range_values:
             widget = QtWidgets.QSpinBox()
             widget.setMinimum(range_values[0])
             widget.setMaximum(range_values[1])
-            if text in ["", None, 'None']:
+            if widget_content in ["", None, 'None']:
                 widget.setValue(range_values[0])
             else:
-                widget.setValue(int(text))
-
-        if data_list is None:
-            set_widget_color(widget)
-            widget.setFixedWidth(40)
-            widget.clicked.connect(
-                lambda: self.row_number_clicked(widget))
-        elif foreign_key:
+                widget.setValue(int(widget_content))
+            change_signal = widget.valueChanged
+        if widget is None:
+            raise
+        return widget, change_signal
+
+    def add_widget(self, row_idx, col_idx, foreign_key=False, choices=None,
+                   range_values=None, plot_type=None, field_name=''
+                   ) -> QWidget:
+        """
+        Add a cell widget at a given row and column in the data_table_widget.
+
+        :param row_idx: index of row
+        :param col_idx: index of column
+        :param foreign_key: True if there is a foreign constrain that prevents
+            the row to be deleted => set readonly of cell=True
+        :param choices: List of choices to add to a drop down list. If
+            it is available, QComboBox will be assigned for the widget.
+        :param range_values: list consists of min and max values. If
+            it is available, QSpinbox will be assigned for the widget
+        :param plot_type: the plot type associated with the value colors
+            contain in the created widget. If it is available, ValueColorEdit
+            will be assigned for the widget
+        If no choices, range, or plot type available, QLineEdit will be
+        assigned for the widget.
+        :param field_name: name of the column in database. If field_name=
+            'convertFactor', add validator to limit precision to 6 decimal
+            points
+        :return: value stored in created widget as a string
+        """
+        widget_content = (str(self.database_rows[row_idx][col_idx - 1])
+                          if row_idx < len(self.database_rows) else '')
+
+        widget, change_signal = self.create_widget(
+            widget_content, choices=choices, range_values=range_values,
+            field_name=field_name, plot_type=plot_type, row_idx=row_idx
+        )
+
+        if foreign_key:
             set_widget_color(widget, read_only=True)
         else:
-            new = False if row_idx < len(data_list) else True
+            new = False if row_idx < len(self.database_rows) else True
             set_widget_color(widget, read_only=False, changed=new)
-            change_signal = None
-            if choices is None and range_values is None:
-                change_signal = widget.textChanged
-            elif choices:
-                change_signal = widget.currentTextChanged
-            elif range_values:
-                change_signal = widget.valueChanged
             change_signal.connect(
                 lambda changed_text:
-                    self.on_cell_input_change(changed_text, widget)
+                self.on_cell_input_change(changed_text, widget)
             )
         self.data_table_widget.setCellWidget(row_idx, col_idx, widget)
         if field_name == 'description':
             self.data_table_widget.resizeRowToContents(row_idx)
-        return text
+        return widget
 
     @QtCore.Slot()
     def row_number_clicked(self, widget):
@@ -353,7 +398,7 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
             self.data_type = self.data_type_combo_box.currentText()
         self.update_data_table_widget_items()
 
-    def get_data_list(self) -> list:
+    def get_database_rows(self) -> list:
         """
         Get list of data to fill self.data_table_widgets' content. Should be
         implemented by all children.
@@ -364,12 +409,12 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
 
     def update_data_table_widget_items(self):
         """
-        Create widget cell for self.data_table_widget based on self.data_list
-        for data to be displayed.
+        Create widget cell for self.data_table_widget based on
+        self.database_rows for data to be displayed.
         Each row will be check for foreign_key constrain to decide readonly
         status.
-        If self.data_list is empty, call clear_first_row to create a row with
-        all necessary widget cells but empty.
+        If self.database_rows is empty, call clear_first_row to create a row
+        with all necessary widget cells but empty.
         """
         # When it comes to deleted rows, we can either reset them manually or
         # remove them altogether. We chose to remove them using this line
@@ -377,8 +422,11 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
         # matter because we don't really expect to show more than 100 channels
         # at a time.
         self.data_table_widget.setRowCount(0)
-        self.database_rows = self.get_data_list()
-        self.is_row_changed_array = [False] * len(self.database_rows)
+        self.database_rows = self.get_database_rows()
+        self.changes_array = [
+            [False] * len(self.database_rows[0])
+            for _ in range(len(self.database_rows))
+        ]
         row_count = len(self.database_rows)
         row_count = 1 if row_count == 0 else row_count
         self.data_table_widget.setRowCount(row_count)
@@ -405,7 +453,7 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
         self.data_table_widget.scrollToBottom()
         self.data_table_widget.repaint()  # to show row's header
         self.data_table_widget.cellWidget(row_position, 1).setFocus()
-        self.is_row_changed_array.append(True)
+        self.changes_array.append([True] * len(self.database_rows[0]))
 
     def remove_row(self, remove_row_idx):
         """
@@ -439,7 +487,7 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
             set_widget_color(cell_widget, changed=changed)
         else:
             changed = True
-        self.is_row_changed_array[row_idx] = changed
+        self.changes_array[row_idx][col_idx - 1] = changed
 
     def check_data_foreign_key(self, val):
         """
@@ -547,12 +595,12 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
         # Because self.changed_rows only track changes to row content and not
         # deletions, we want to remove the deleted row's ID from it.
         if row_idx < len(self.database_rows):
-            self.is_row_changed_array[row_idx] = False
+            self.changes_array[row_idx] = [False] * len(self.database_rows[0])
         else:
             # Because rows not in the database are removed from the table when
             # deleted, we delete the value that tracks whether they changed
             # when they are deleted.
-            self.is_row_changed_array.pop(row_idx)
+            self.changes_array.pop(row_idx)
 
         if row_idx >= len(self.database_rows):
             # Deleting a row that is not in the database. Because no rows that
@@ -602,7 +650,7 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
 
     def data_type_changed(self):
         """
-        Load new self.data_list when self.data_type_combo_box's value is
+        Load new self.database_rows when self.data_type_combo_box's value is
             changed
         """
         pass
@@ -630,13 +678,13 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
                  False otherwise
         """
         return (len(self.queued_row_delete_sqls) != 0 or
-                any(self.is_row_changed_array))
+                any([any(row_changes) for row_changes in self.changes_array]))
 
     def untrack_changes(self):
         """
         Untrack all changes that have been made.
         """
-        self.is_row_changed_array = []
+        self.changes_array = []
         self.queued_row_delete_sqls = {}
 
     def validate_row(self, row_id: int, row_content: List) -> Tuple[bool, str]:
@@ -693,9 +741,9 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
                 no issue
         """
         changed_row_ids = [idx
-                           for (idx, is_changed)
-                           in enumerate(self.is_row_changed_array)
-                           if is_changed]
+                           for (idx, is_cell_changed_in_row_array)
+                           in enumerate(self.changes_array)
+                           if any(is_cell_changed_in_row_array)]
         for row_id in changed_row_ids:
             row_content = self.get_row_inputs(row_id)
             is_row_valid, msg = self.validate_row(row_id, row_content)
@@ -737,9 +785,9 @@ class UiDBInfoDialog(OneWindowAtATimeDialog):
             return False
 
         changed_row_ids = [idx
-                           for (idx, is_changed)
-                           in enumerate(self.is_row_changed_array)
-                           if is_changed]
+                           for (idx, is_cell_changed_in_row_array)
+                           in enumerate(self.changes_array)
+                           if any(is_cell_changed_in_row_array)]
         for row_id in changed_row_ids:
             if row_id < len(self.database_rows):
                 current_primary_key = self.database_rows[row_id][0]
diff --git a/sohstationviewer/view/db_config/edit_single_param_dialog.py b/sohstationviewer/view/db_config/edit_single_param_dialog.py
index 3c38599ea41d27b11e90e13f1217c966f7b34f2e..72a31715b27967caeee89f6d35a07d9588affb6d 100755
--- a/sohstationviewer/view/db_config/edit_single_param_dialog.py
+++ b/sohstationviewer/view/db_config/edit_single_param_dialog.py
@@ -6,7 +6,7 @@ from typing import Optional, Dict
 from PySide6 import QtWidgets
 from PySide6.QtWidgets import QWidget, QDialog, QLineEdit
 
-from sohstationviewer.view.util.plot_func_names import plot_functions
+from sohstationviewer.view.util.plot_type_info import plot_types
 
 from sohstationviewer.database.process_db import execute_db
 from sohstationviewer.database.extract_data import (
@@ -39,7 +39,7 @@ class EditSingleParamDialog(QDialog):
 
         # parameter's plot type which decides the shape of the plot
         self.plot_type_cbo_box = QtWidgets.QComboBox(self)
-        self.plot_type_cbo_box.addItems([""] + list(plot_functions.keys()))
+        self.plot_type_cbo_box.addItems([""] + list(plot_types.keys()))
 
         # value color in black mode
         self.value_colorb_widget = QLineEdit(self)
diff --git a/sohstationviewer/view/db_config/param_dialog.py b/sohstationviewer/view/db_config/param_dialog.py
index a7e4d4ce96cb1e7e5d5479ea8140f5f8b684314e..9fafc2995ffe5808dee3fec1b9560a5e3871b74d 100755
--- a/sohstationviewer/view/db_config/param_dialog.py
+++ b/sohstationviewer/view/db_config/param_dialog.py
@@ -3,20 +3,22 @@ param_dialog.py
 GUI to add/dit/remove params
 NOTE: Cannot remove or change params that are already used for channels.
 """
+from typing import Optional, List, Tuple
+
 from PySide6 import QtWidgets, QtCore
-from PySide6.QtCore import Qt
+from PySide6.QtCore import Qt, Signal
 from PySide6.QtWidgets import QComboBox, QWidget
 
 from sohstationviewer.conf.constants import ColorMode, ALL_COLOR_MODES
-from sohstationviewer.view.util.plot_func_names import plot_functions
+from sohstationviewer.view.db_config.value_color_helper.value_color_edit \
+    import ValueColorEdit
+from sohstationviewer.view.util.plot_type_info import plot_types
 from sohstationviewer.view.db_config.db_config_dialog import (
     UiDBInfoDialog,
 )
 
 from sohstationviewer.database.process_db import execute_db
 
-from sohstationviewer.conf.dbSettings import dbConf
-
 
 class ParamDialog(UiDBInfoDialog):
     def __init__(self, parent: QWidget, color_mode: ColorMode) -> None:
@@ -26,9 +28,9 @@ class ParamDialog(UiDBInfoDialog):
         """
         self.color_mode = color_mode
 
-        self.require_valuecolors_plottypes = [
-            p for p in plot_functions.keys()
-            if 'ValueColors' in plot_functions[p]['description']]
+        self.plot_types_with_value_colors = [
+            p for p in plot_types.keys()
+            if 'pattern' in plot_types[p]]
         super().__init__(
             parent,
             ['No.', 'Param', 'Plot Type', 'ValueColors', 'Height    '],
@@ -47,6 +49,69 @@ class ParamDialog(UiDBInfoDialog):
         self.setWindowTitle("Edit/Add/Delete Parameters")
         self.add_color_selector(color_mode)
 
+    def create_widget(self, widget_content: str,
+                      choices: Optional[List] = None,
+                      range_values: Optional[List] = None,
+                      plot_type: Optional[str] = None, field_name: str = '',
+                      row_idx: int = -1, **kwargs) -> Tuple[QWidget, Signal]:
+        """
+        Create a widget with the given content. The actual type of widget
+        created depends on the arguments passed in. This method delegates to
+        the parent's implementation unless plot_type is not None.
+
+        :param widget_content: the content of the created widget
+        :param choices: list of choices to add to a drop-down list. If this is
+            available, the created widget will be a QComboBox.
+        :param range_values: list consists of min and max values. If this is
+            available, the created widget will be a QSpinbox.
+        :param plot_type: the plot type associated with the value colors
+            contain in the created widget. If this is available, the created
+            widget will be a ValueColorEdit.
+        :param field_name: name of the column in database.
+            If field_name is 'convertFactor', a validator will be added to the
+            created widget, limiting the input to numbers in the range 0.0-5.0
+            with at most 6 decimal digits.
+            If field_name is 'description', the created widget will be a
+            QTextEdit.
+        :param row_idx: the index of the row the created widget will be placed
+            in. Used to determine the original plot type contained in the row.
+        :return:
+            - a widget whose type depends on the arguments passed in.
+            - the QT signal that is emitted when the content of the returned
+                widget is modified
+        """
+        if plot_type is not None:
+            # Rows that are not in the database do not have a plot type, so we
+            # consider the passed in plot type the original plot type. This
+            # choice was made to allow for the easiest implementation of the
+            # intended behavior.
+            original_plot_type = (
+                self.database_rows[row_idx][1]
+                if row_idx < len(self.database_rows)
+                else plot_type
+            )
+            # A string that is known to not be a value colors string. Only
+            # used to make sure that the ValueColorEdit created later is forced
+            # to use the default value colors for the passed in plot type, so
+            # the actual string does not matter.
+            BAD_VALUE_COLORS = 'VC'
+            value_colors = (
+                widget_content
+                if original_plot_type == plot_type
+                else BAD_VALUE_COLORS
+            )
+            # We create a new ValueColorEdit and let the created widget decides
+            # what its content should be. This is far easier than managing the
+            # value we want the created widget to have.
+            widget = ValueColorEdit(self.data_table_widget, self.color_mode,
+                                    plot_type, value_colors)
+            change_signal = widget.value_color_edited
+        else:
+            widget, change_signal = super().create_widget(
+                widget_content, choices, range_values, field_name
+            )
+        return widget, change_signal
+
     def add_color_selector(self, initial_color_mode: ColorMode) -> None:
         """
         Add a color mode selector widget to the dialog.
@@ -73,28 +138,24 @@ class ParamDialog(UiDBInfoDialog):
         :param fk: bool: True if there is a foreign constrain that prevents the
             row to be deleted
         """
-        self.add_widget(None, row_idx, 0)  # No.
-        self.add_widget(self.database_rows, row_idx, 1, foreign_key=fk)
-        plot_type = self.add_widget(
-            self.database_rows, row_idx, 2,
-            choices=[''] + sorted(plot_functions.keys()))
-        place_holder_text = ""
-        if plot_type in self.require_valuecolors_plottypes:
-            place_holder_text = "Enter ValueColors..."
-            if plot_type == 'linesDots':
-                place_holder_text = "Ex: L:G|D:W"
-            elif plot_type == 'multiColorDots':
-                place_holder_text = "Ex: -1:_|0:R|2.3:Y|+2.3:G"
-            elif plot_type == "upDownDots":
-                place_holder_text = "Ex: 1:R|0:Y"
-            elif plot_type == "dotForTime":
-                place_holder_text = "Ex: G"
-        self.add_widget(self.database_rows, row_idx, 3,
-                        place_holder_text=place_holder_text)
-        self.add_widget(self.database_rows, row_idx, 4, range_values=[0, 10])
+        self.add_row_number_button(row_idx)  # No.
+        self.add_widget(row_idx, 1, foreign_key=fk)
+        plot_type_selector = self.add_widget(
+            row_idx, 2, choices=[''] + sorted(plot_types.keys())
+        )
+        # ValueColorEdit was designed so that each instance only handles one
+        # plot type, so we need to create a new one whenever the user changes
+        # the plot type.
+        plot_type_selector.currentTextChanged.connect(
+            lambda new_plot_type: self.add_widget(row_idx, 3,
+                                                  plot_type=new_plot_type)
+        )
+        self.add_widget(row_idx, 3, plot_type=plot_type_selector.currentText())
+
+        self.add_widget(row_idx, 4, range_values=[0, 10])
         self.add_delete_button_to_row(row_idx, fk)
 
-    def get_data_list(self):
+    def get_database_rows(self):
         """
         Get list of data to fill self.data_table_widgets' content
         """
@@ -120,7 +181,7 @@ class ParamDialog(UiDBInfoDialog):
         return [
             self.data_table_widget.cellWidget(row_idx, 1).text().strip(),
             self.data_table_widget.cellWidget(row_idx, 2).currentText(),
-            self.data_table_widget.cellWidget(row_idx, 3).text().strip(),
+            self.data_table_widget.cellWidget(row_idx, 3).value_color_str,
             int(self.data_table_widget.cellWidget(row_idx, 4).value())
         ]
 
@@ -147,11 +208,11 @@ class ParamDialog(UiDBInfoDialog):
             return False, err_msg
 
         if value_colors_string != "":
-            if plot_type in self.require_valuecolors_plottypes:
+            if plot_type in self.plot_types_with_value_colors:
                 value_colors = value_colors_string.split("|")
                 for vc in value_colors:
                     vc = vc.strip()
-                    plot_type_info = dbConf.get(plot_type)
+                    plot_type_info = plot_types.get(plot_type)
                     if plot_type_info is None:
                         continue
                     if not plot_type_info['pattern'].match(vc):
diff --git a/sohstationviewer/view/db_config/plot_type_dialog.py b/sohstationviewer/view/db_config/plot_type_dialog.py
index 982c177f19a4944ae95a31f462f07001f6abe815..af3be9d964165f03dfdc6218993a815eac486cbf 100755
--- a/sohstationviewer/view/db_config/plot_type_dialog.py
+++ b/sohstationviewer/view/db_config/plot_type_dialog.py
@@ -3,7 +3,7 @@ GUI to view the types of plotting and their descriptions
 NOTE: plot's types are defined in __init__.plot_functions
 """
 
-from sohstationviewer.view.util.plot_func_names import plot_functions
+from sohstationviewer.view.util.plot_type_info import plot_types
 from sohstationviewer.view.db_config.db_config_dialog import UiDBInfoDialog
 
 
@@ -23,14 +23,13 @@ class PlotTypeDialog(UiDBInfoDialog):
             plot's types are based on plotting functions which are hard code
             so it's needed to set to True to prevent the row to be deleted
         """
-        self.add_widget(None, row_idx, 0)       # No.
-        self.add_widget(self.database_rows, row_idx, 1, foreign_key=True)
-        self.add_widget(self.database_rows, row_idx, 2, foreign_key=True,
-                        field_name='description')
+        self.add_row_number_button(row_idx)       # No.
+        self.add_widget(row_idx, 1, foreign_key=True)
+        self.add_widget(row_idx, 2, foreign_key=True, field_name='description')
 
-    def get_data_list(self):
+    def get_database_rows(self):
         """
         Get list of data to fill self.data_table_widgets' content
         """
         return [[key, val['description']]
-                for key, val in plot_functions.items()]
+                for key, val in plot_types.items()]
diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/__init__.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ec4ed7912a67bc19c8834305e1a3097623bf2a10 100644
--- a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/__init__.py
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/__init__.py
@@ -0,0 +1,6 @@
+from .edit_value_color_dialog import *  # noqa: F403, F401
+from .line_dot_dialog import *  # noqa: F403, F401
+from .multi_color_dot_dialog import *  # noqa: F403, F401
+from .tri_color_lines_dialog import *  # noqa: F403, F401
+from .up_down_dialog import *  # noqa: F403, F401
+from .dot_for_time_dialog import *  # noqa: F403, F401
diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/dot_for_time_dialog.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/dot_for_time_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1a067e339ccbe35d46a642811f379dd60d995d1
--- /dev/null
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/dot_for_time_dialog.py
@@ -0,0 +1,58 @@
+from typing import Optional
+
+from PySide6.QtWidgets import QWidget, QPushButton, QLabel
+
+from sohstationviewer.view.db_config.value_color_helper. \
+    edit_value_color_dialog.edit_value_color_dialog import (
+        EditValueColorDialog, display_color,
+    )
+
+
+class DotForTimeDialog(EditValueColorDialog):
+    def __init__(self, parent: Optional[QWidget], value_color_str: str):
+        """
+        :param parent: the parent widget
+        :param value_color_str: string for value color to be saved in DB
+        """
+        # Widget that allow user to add/edit down's color
+        self.select_color_button = QPushButton("Select Color")
+        # Widget to display down's color
+        self.color_label = QLabel()
+        self.color_label.setFixedWidth(30)
+        self.color_label.setAutoFillBackground(True)
+
+        super().__init__(parent, value_color_str)
+        self.setWindowTitle("Edit Dot For Time Plot's Colors")
+
+    def setup_ui(self):
+        self.main_layout.addWidget(QLabel('Dot Color'), 0, 0, 1, 1)
+        self.main_layout.addWidget(self.color_label, 0, 1, 1, 1)
+        self.main_layout.addWidget(self.select_color_button, 0, 2, 1, 1)
+
+        self.setup_complete_buttons(1)
+
+    def connect_signals(self) -> None:
+        self.select_color_button.clicked.connect(
+            lambda: self.on_select_color(self.color_label))
+        super().connect_signals()
+
+    def set_value(self):
+        """
+        Change the corresponding color_labels's color according to the color
+            from value_color_str.
+        """
+        if self.value_color_str == "":
+            return
+        vc_parts = self.value_color_str.split('|')
+        for vc_str in vc_parts:
+            obj_type, color = vc_str.split(':')
+            display_color(self.color_label, color)
+
+    def save_color(self):
+        """
+        Create value_color_str from GUI's info and close the GUI with color
+            is the hex color got from color_labels' color
+        """
+        color = self.color_label.palette().window().color().name()
+        self.value_color_str = f"Color:{color.upper()}"
+        self.accept()
diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/edit_value_color_dialog_super_class.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/edit_value_color_dialog.py
similarity index 95%
rename from sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/edit_value_color_dialog_super_class.py
rename to sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/edit_value_color_dialog.py
index 54184aac8dfaa81b6458506ab1585dd9d87fcf46..a9da792c10fe30c01748679ff87597304a1be590 100644
--- a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/edit_value_color_dialog_super_class.py
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/edit_value_color_dialog.py
@@ -39,6 +39,9 @@ class EditValueColorDialog(QDialog):
     def set_value(self):
         pass
 
+    def save_color(self):
+        pass
+
     def setup_complete_buttons(self, row_total) -> None:
         """
         :param row_total: total of rows to edit
@@ -48,7 +51,7 @@ class EditValueColorDialog(QDialog):
 
     def connect_signals(self) -> None:
         self.cancel_btn.clicked.connect(self.close)
-        self.save_colors_btn.clicked.connect(self.on_save_color)
+        self.save_colors_btn.clicked.connect(self.save_color)
 
     def on_select_color(self, color_label: QtWidgets.QLabel):
         """
diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/line_dot_dialog.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/line_dot_dialog.py
index 7479142baa54ec84754dbffdbb4a9f4d1bbe9337..9b2a07c8dec29bc30dbab64af93bfae92637831a 100644
--- a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/line_dot_dialog.py
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/line_dot_dialog.py
@@ -6,7 +6,7 @@ from PySide6 import QtWidgets
 from PySide6.QtWidgets import QWidget
 
 from sohstationviewer.view.db_config.value_color_helper.\
-    edit_value_color_dialog.edit_value_color_dialog_super_class import \
+    edit_value_color_dialog.edit_value_color_dialog import \
     EditValueColorDialog, display_color
 
 
@@ -84,17 +84,17 @@ class LineDotDialog(EditValueColorDialog):
                 display_color(self.dot_color_label, color)
                 self.dot_include_chkbox.setChecked(True)
 
-    def on_save_color(self):
+    def save_color(self):
         """
         Create value_color_str from GUI's info and close the GUI with color
             is the hex color got from color_labels' color
         """
         line_color = self.line_color_label.palette().window().color().name()
-        self.value_color_str = f"Line:{line_color}"
+        self.value_color_str = f"Line:{line_color.upper()}"
         if self.dot_include_chkbox.isChecked():
             dot_color = self.dot_color_label.palette().window().color().name()
-            self.value_color_str += f"|Dot:{dot_color}"
-        self.close()
+            self.value_color_str += f"|Dot:{dot_color.upper()}"
+        self.accept()
 
 
 if __name__ == '__main__':
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
index b85afbac7718bc6a791e50b87d501f98fbe47d01..ee7ab9e15dfe5ab1df48c79bc9d23129b08b36cf 100644
--- 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
@@ -7,7 +7,7 @@ from PySide6 import QtWidgets, QtCore, QtGui
 from PySide6.QtWidgets import QWidget
 
 from sohstationviewer.view.db_config.value_color_helper.\
-    edit_value_color_dialog.edit_value_color_dialog_super_class import \
+    edit_value_color_dialog.edit_value_color_dialog import \
     EditValueColorDialog, display_color
 
 
@@ -101,6 +101,11 @@ class MultiColorDotDialog(EditValueColorDialog):
                     self.include_greater_than_chkbox, ROW_TOTAL - 1, 0, 1, 1)
             (lower_bound_lnedit, higher_bound_lnedit,
              select_color_btn, color_label) = self.add_row(i)
+            if i == 0:
+                # We provide a default value for the first higher bound editor
+                # because it is the only one that is included in the saved
+                # value colors string by default.
+                higher_bound_lnedit.setText('0')
             self.lower_bound_lnedits.append(lower_bound_lnedit)
             self.higher_bound_lnedits.append(higher_bound_lnedit)
             self.select_color_btns.append(select_color_btn)
@@ -306,7 +311,7 @@ class MultiColorDotDialog(EditValueColorDialog):
         self.lower_bound_lnedits[row_id + 1].setText(str(curr_higher_bound))
         self.set_value_row_id_dict()
         if self.save_colors_btn_clicked:
-            self.on_save_color()
+            self.save_color()
         if self.select_color_btn_clicked:
             self.on_select_color(row_id)
 
@@ -382,7 +387,7 @@ class MultiColorDotDialog(EditValueColorDialog):
             self.set_color_enabled(vc_idx, True)
             display_color(self.color_labels[vc_idx], color)
 
-    def on_save_color(self):
+    def save_color(self):
         """
         Create value_color_str from GUI's info and close the GUI.
             + Skip row that has no color
@@ -405,7 +410,7 @@ class MultiColorDotDialog(EditValueColorDialog):
                 continue
             value = self.higher_bound_lnedits[i].text()
             color = self.color_labels[i].palette(
-            ).window().color().name()
+            ).window().color().name().upper()
             if i == 0 and not self.include_less_than_chkbox.isChecked():
                 color = 'not plot'
             operator = '<=' if self.upper_equal else '<'
@@ -413,7 +418,7 @@ class MultiColorDotDialog(EditValueColorDialog):
         if self.include_greater_than_chkbox.isChecked():
 
             color = self.color_labels[ROW_TOTAL - 1].palette().window(
-                ).color().name()
+                ).color().name().upper()
             val = f"{self.lower_bound_lnedits[ROW_TOTAL - 1].text()}"
             if self.upper_equal:
                 value_color_list.append(f"{val}<:{color}")
@@ -421,7 +426,7 @@ class MultiColorDotDialog(EditValueColorDialog):
                 value_color_list.append(f"={val}:{color}")
 
         self.value_color_str = '|'.join(value_color_list)
-        self.close()
+        self.accept()
 
 
 class MultiColorDotLowerEqualDialog(MultiColorDotDialog):
diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/tri_color_lines_dialog.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/tri_color_lines_dialog.py
index 9178cdad920f6bb4cbc892a3b5aed8e51bbdb34e..7adc36c882a023145c73ae69d8997b0e76f4d098 100644
--- a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/tri_color_lines_dialog.py
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/tri_color_lines_dialog.py
@@ -6,7 +6,7 @@ from PySide6 import QtWidgets
 from PySide6.QtWidgets import QWidget
 
 from sohstationviewer.view.db_config.value_color_helper.\
-    edit_value_color_dialog.edit_value_color_dialog_super_class import \
+    edit_value_color_dialog.edit_value_color_dialog import \
     EditValueColorDialog, display_color
 
 
@@ -82,7 +82,7 @@ class TriColorLinesDialog(EditValueColorDialog):
             if val == '-1':
                 display_color(self.neg_one_color_label, color)
 
-    def on_save_color(self):
+    def save_color(self):
         """
         Create value_color_str from GUI's info and close the GUI with color
             is the hex color got from color_labels' color
@@ -93,9 +93,10 @@ class TriColorLinesDialog(EditValueColorDialog):
         neg_one_color = self.neg_one_color_label.palette() \
             .window().color().name()
 
-        self.value_color_str = (f"-1:{neg_one_color}|0:{zero_color}"
-                                f"|1:{pos_one_color}")
-        self.close()
+        self.value_color_str = (f"-1:{neg_one_color.upper()}"
+                                f"|0:{zero_color.upper()}"
+                                f"|1:{pos_one_color.upper()}")
+        self.accept()
 
 
 if __name__ == '__main__':
diff --git a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/up_down_dialog.py b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/up_down_dialog.py
index 69605b90b600a2f9a84ff00750904f0bb7d96a9d..0e355f17588266a393381f2f99a1f04971bfcd58 100644
--- a/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/up_down_dialog.py
+++ b/sohstationviewer/view/db_config/value_color_helper/edit_value_color_dialog/up_down_dialog.py
@@ -6,7 +6,7 @@ from PySide6 import QtWidgets
 from PySide6.QtWidgets import QWidget
 
 from sohstationviewer.view.db_config.value_color_helper.\
-    edit_value_color_dialog.edit_value_color_dialog_super_class import \
+    edit_value_color_dialog.edit_value_color_dialog import \
     EditValueColorDialog, display_color
 
 
@@ -66,15 +66,16 @@ class UpDownDialog(EditValueColorDialog):
             if obj_type == 'Down':
                 display_color(self.down_color_label, color)
 
-    def on_save_color(self):
+    def save_color(self):
         """
         Create value_color_str from GUI's info and close the GUI with color
             is the hex color got from color_labels' color
         """
         up_color = self.up_color_label.palette().window().color().name()
         down_color = self.down_color_label.palette().window().color().name()
-        self.value_color_str = f"Up:{up_color}|Down:{down_color}"
-        self.close()
+        self.value_color_str = (f"Up:{up_color.upper()}"
+                                f"|Down:{down_color.upper()}")
+        self.accept()
 
 
 if __name__ == '__main__':
diff --git a/sohstationviewer/view/db_config/value_color_helper/functions.py b/sohstationviewer/view/db_config/value_color_helper/functions.py
index da6aeb07198191c6a5b49c3405ecfbad77d95c24..42e78ba68302685f644b749f4a5cfdbbfa936b5d 100644
--- a/sohstationviewer/view/db_config/value_color_helper/functions.py
+++ b/sohstationviewer/view/db_config/value_color_helper/functions.py
@@ -1,12 +1,12 @@
-from typing import Optional
+import re
 
 from sohstationviewer.view.util.color import clr
 
-from sohstationviewer.view.util.plot_func_names import plot_functions
+from sohstationviewer.view.util.plot_type_info import plot_types
 
 
 def convert_value_color_str(
-        plot_type: str, old_value_color_str: Optional[str]) -> str:
+        plot_type: str, old_value_color_str: str) -> str:
     """
     Convert value_color str to new format. This will be removed after
         value_colors in database changed
@@ -22,8 +22,8 @@ def convert_value_color_str(
     :param old_value_color_str: value_color_str in old format
     :return: value_color_str in new format
     """
-    if old_value_color_str is None:
-        return ""
+    if old_value_color_str == '':
+        return ''
     value_color_list = []
     if old_value_color_str == '' and plot_type == 'linesDots':
         old_value_color_str = "L:G"
@@ -47,17 +47,24 @@ def convert_value(plot_type: str, old_value: str):
     :param old_value: value in old format
     :return: value in new format
     """
-    if not plot_functions[plot_type]['value_pattern'].match(old_value):
-        return "unrecognized:" + old_value
+    value_pattern = plot_types[plot_type].get('value_pattern',
+                                              re.compile('.?'))
+    if not value_pattern.match(old_value):
+        raise ValueError(f'The old value does not match the pattern for plot '
+                         f'type {plot_type}.')
 
     if old_value in ['L', 'Line'] and plot_type == 'linesDots':
         new_value = 'Line'
     elif old_value in ['D', 'Dot'] and plot_type == 'linesDots':
         new_value = 'Dot'
+    elif old_value in ['Z', 'Zero'] and plot_type == 'linesDots':
+        new_value = 'Zero'
     elif old_value in ['1', 'Up'] and plot_type == 'upDownDots':
         new_value = 'Up'
     elif old_value in ['0', 'Down'] and plot_type == 'upDownDots':
         new_value = 'Down'
+    elif old_value in ['C', 'Color'] and plot_type == 'dotForTime':
+        new_value = 'Color'
     elif plot_type == "multiColorDotsEqualOnUpperBound":
         if old_value.startswith('+'):
             new_value = old_value[1:] + '<'
@@ -73,11 +80,15 @@ def convert_value(plot_type: str, old_value: str):
     elif plot_type == 'triColorLines':
         new_value = old_value
     else:
-        new_value = "Sth wrong:" + old_value
+        raise ValueError(f'Something went wrong while converting the value '
+                         f'part of a valueColors. Please make sure '
+                         f'{plot_type} is a valid plot type and {old_value} '
+                         f'is a valid value part of a valueColors for the '
+                         f'plot type.')
     return new_value
 
 
-def prepare_value_color_html(value_colors: Optional[str]) -> str:
+def prepare_value_color_html(value_colors: str) -> str:
     """
     Change value_color with hex color to html to square with actual color from
         hex color.
@@ -91,8 +102,8 @@ def prepare_value_color_html(value_colors: Optional[str]) -> str:
         Ex: <p>Line:<span style='#00FFFF; font-size:25px;'>&#8718;</span>|
              Dot:<span style='#FF0000; font-size:25px;'>&#8718;</span></p>
     """
-    if value_colors in [None, ""]:
-        return ""
+    if value_colors == '':
+        return ''
     html_color_parts = []
     color_parts = value_colors.split('|')
     for c_str in color_parts:
diff --git a/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py b/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a57c3e226a1ee3321f0fb42d5950b39bb2f117f
--- /dev/null
+++ b/sohstationviewer/view/db_config/value_color_helper/value_color_edit.py
@@ -0,0 +1,145 @@
+from typing import Optional, Type
+
+from PySide6 import QtWidgets, QtGui, QtCore
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import QWidget, QTextEdit
+
+from sohstationviewer.conf.constants import ROOT_PATH
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog import EditValueColorDialog
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog import LineDotDialog
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog import (
+        MultiColorDotLowerEqualDialog, MultiColorDotUpperEqualDialog)
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog import TriColorLinesDialog
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog import UpDownDialog
+from sohstationviewer.view.db_config.value_color_helper.\
+    edit_value_color_dialog import DotForTimeDialog
+from sohstationviewer.view.db_config.value_color_helper.functions import \
+    prepare_value_color_html
+from sohstationviewer.view.util.plot_type_info import plot_types
+
+
+plot_types_with_value_colors = [
+            p for p in plot_types
+            if 'pattern' in plot_types[p]]
+
+
+class ValueColorEdit(QTextEdit):
+    value_color_edited = QtCore.Signal(str)
+
+    def __init__(self, parent: Optional[QWidget], background: str,
+                 plot_type: str, value_color_str: str = ''):
+        """
+        Widget to display valueColors and call a dialog to edit tha value
+        :param parent: the parent widget
+        :param background: 'B'/'W': flag indicating background color
+        :param plot_type: the plot type the editor used to format and validate
+            the value colors
+        :param value_color_str: the initial value colors shown in the editor.
+            If this does not fit the format required by the given plot type,
+            use the default value colors for the plot type instead.
+        """
+        QtWidgets.QTextEdit.__init__(self, parent)
+        self.set_background(background)
+        # type of channel's plot
+        self.plot_type: str = plot_type
+        self.value_color_str: str = ''
+        if plot_type in plot_types_with_value_colors:
+            try:
+                self.set_value_color(value_color_str)
+            except ValueError:
+                # We set the value colors to the default value (specified in
+                # plot_type_info.py) when there is a problem with the given
+                # value colors string.
+                self.set_value_color(
+                    plot_types[plot_type]['default_value_color']
+                )
+
+        # dialog that pop up when clicking on edit_button to help edit color
+        # and value
+        self.edit_value_color_dialog: Optional[QWidget] = None
+
+        self.setReadOnly(True)
+        # change cursor to Arrow so user know they can't edit directly
+        self.viewport().setCursor(
+            QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)
+        )
+        # to see all info
+        self.setFixedHeight(28)
+        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.verticalScrollBar().setDisabled(True)
+
+        self.edit_button = QtWidgets.QToolButton(self)
+        self.edit_button.setCursor(Qt.PointingHandCursor)
+        if background == 'B':
+            img_file = f"{ROOT_PATH}/images/edit_icon_black_background.png"
+        else:
+            img_file = f"{ROOT_PATH}/images/edit_icon_white_background.png"
+        self.edit_button.setIcon(QtGui.QIcon(img_file))
+        self.edit_button.setStyleSheet(
+            "background: transparent; border: none;")
+        self.edit_button.clicked.connect(self.edit)
+        if plot_type not in plot_types_with_value_colors:
+            self.edit_button.hide()
+
+        layout = QtWidgets.QHBoxLayout(self)
+        layout.addWidget(self.edit_button, 0, Qt.AlignRight)
+
+        layout.setSpacing(0)
+        layout.setContentsMargins(5, 5, 5, 5)
+
+        self.textChanged.connect(
+            lambda: self.value_color_edited.emit(self.value_color_str)
+        )
+
+    def set_background(self, background: str):
+        """
+        Set black background for user to have better feeling how the colors
+            displayed on black background. Text and PlaceholderText's colors
+            have to be changed to be readable on the black background too.
+        :param background: 'B'/'W': sign for background color
+        """
+        palette = self.palette()
+        if background == 'B':
+            palette.setColor(QtGui.QPalette.ColorRole.Text, Qt.white)
+            palette.setColor(QtGui.QPalette.ColorRole.Base, Qt.black)
+            palette.setColor(QtGui.QPalette.ColorRole.PlaceholderText,
+                             Qt.lightGray)
+            self.setPalette(palette)
+
+    def set_value_color(self, value_color_str: str) -> None:
+        """
+        Set value_color_str, value_color_edit_dialog and display value color
+            string in html to show color for user to have the feeling.
+        :param value_color_str: string for value color to be saved in DB
+        """
+        self.value_color_str = value_color_str
+        value_color_html = prepare_value_color_html(self.value_color_str)
+        self.setHtml(value_color_html)
+
+    def edit(self):
+        """
+        Show user an editor to edit the value colors of this widget.
+        """
+        if self.plot_type not in plot_types_with_value_colors:
+            return
+        plot_type_dialog_map: dict[str, Type[EditValueColorDialog]] = {
+            'linesDots': LineDotDialog,
+            'triColorLines': TriColorLinesDialog,
+            'dotForTime': DotForTimeDialog,
+            'multiColorDotsEqualOnUpperBound': MultiColorDotUpperEqualDialog,
+            'multiColorDotsEqualOnLowerBound': MultiColorDotLowerEqualDialog,
+            'upDownDots': UpDownDialog,
+        }
+        edit_dialog = plot_type_dialog_map[self.plot_type](
+            self, self.value_color_str
+        )
+        edit_dialog.accepted.connect(
+            lambda: self.set_value_color(edit_dialog.value_color_str)
+        )
+        edit_dialog.open()
diff --git a/sohstationviewer/view/db_config/value_color_helper/value_color_widget.py b/sohstationviewer/view/db_config/value_color_helper/value_color_widget.py
deleted file mode 100644
index d540879c134d8ebc1e0f1ded375fb06edc46caf6..0000000000000000000000000000000000000000
--- a/sohstationviewer/view/db_config/value_color_helper/value_color_widget.py
+++ /dev/null
@@ -1,139 +0,0 @@
-import sys
-import platform
-import os
-from pathlib import Path
-from typing import Optional
-
-from PySide6 import QtWidgets, QtGui, QtCore
-from PySide6.QtCore import Qt
-from PySide6.QtWidgets import QWidget, QDialog, QTextEdit
-
-from sohstationviewer.view.db_config.value_color_helper.functions import \
-    convert_value_color_str, prepare_value_color_html
-
-
-class ValueColorWidget(QTextEdit):
-    def __init__(self, parent: Optional[QWidget], background: str):
-        """
-        Widget to display valueColors and call a dialog to edit tha value
-        :param parent: the parent widget
-        :param background: 'B'/'W': flag indicating background color
-        """
-        QtWidgets.QTextEdit.__init__(self, parent)
-        self.set_background(background)
-        # string for value color to be saved in DB
-        self.value_color_str: str = ''
-        # dialog that pop up when clicking on edit_button to help edit color
-        # and value
-        self.edit_value_color_dialog: Optional[QWidget] = None
-        # type of channel's plot
-        self.plot_type: str = ''
-
-        self.setReadOnly(True)
-        # change cursor to Arrow so user know they can't edit directly
-        self.viewport().setCursor(
-            QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)
-        )
-        # to see all info
-        self.setFixedHeight(28)
-        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
-        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
-        self.verticalScrollBar().setDisabled(True)
-
-        self.edit_button = QtWidgets.QToolButton(self)
-        self.edit_button.setCursor(Qt.PointingHandCursor)
-        current_file_path = os.path.abspath(__file__)
-        root_path = Path(current_file_path).parent.parent.parent.parent
-        if background == 'B':
-            img_file = f"{root_path}/images/edit_icon_black_background.png"
-        else:
-            img_file = f"{root_path}/images/edit_icon_white_background.png"
-        self.edit_button.setIcon(QtGui.QIcon(img_file))
-        self.edit_button.setStyleSheet(
-            "background: transparent; border: none;")
-        self.edit_button.clicked.connect(self.on_edit)
-
-        layout = QtWidgets.QHBoxLayout(self)
-        layout.addWidget(self.edit_button, 0, Qt.AlignRight)
-
-        layout.setSpacing(0)
-        layout.setContentsMargins(5, 5, 5, 5)
-
-    def set_background(self, background: str):
-        """
-        Set black background for user to have better feeling how the colors
-            displayed on black background. Text and PlaceholderText's colors
-            have to be changed to be readable on the black background too.
-        :param background: 'B'/'W': sign for background color
-        """
-        palette = self.palette()
-        if background == 'B':
-            palette.setColor(QtGui.QPalette.ColorRole.Text, Qt.white)
-            palette.setColor(QtGui.QPalette.ColorRole.Base, Qt.black)
-            palette.setColor(QtGui.QPalette.ColorRole.PlaceholderText,
-                             Qt.lightGray)
-            self.setPalette(palette)
-
-    def set_value_color(self, plot_type: str, value_color_str: str) \
-            -> None:
-        """
-        Set value_color_str, value_color_edit_dialog and display value color
-            string in html to show color for user to have the feeling.
-        :param plot_type: type of channel's plot
-        :param value_color_str: string for value color to be saved in DB
-        """
-
-        self.plot_type = plot_type
-        # Won't need to convert after database's valueColors are changed
-        self.value_color_str = convert_value_color_str(
-            plot_type, value_color_str)
-        value_color_html = prepare_value_color_html(self.value_color_str)
-        self.setHtml(value_color_html)
-
-    def on_edit(self):
-        print('edit value color')
-
-
-class TestDialog(QDialog):
-    def __init__(self):
-        super(TestDialog, self).__init__(None)
-        main_layout = QtWidgets.QVBoxLayout()
-        self.setLayout(main_layout)
-
-        self.value_colorb_widget = ValueColorWidget(self, 'B')
-        main_layout.addWidget(self.value_colorb_widget)
-        self.value_colorw_widget = ValueColorWidget(self, 'W')
-        main_layout.addWidget(self.value_colorw_widget)
-
-        # linesDots
-        self.value_colorb_widget.set_value_color('linesDots', 'L:R|D:G')
-        self.value_colorw_widget.set_value_color('linesDots', 'L:R|D:G')
-
-        # triColorLines
-        # self.value_colorb_widget.set_value_color(
-        #     'triColorLines', '-1:M|0:R|1:G')
-        # self.value_colorw_widget.set_value_color(
-        #     'triColorLines', '-1:M|0:R|1:G')
-
-        # multiColorDotsEqualOnUpperBound
-        # self.value_colorb_widget.set_value_color(
-        #     'multiColorDotsEqualOnUpperBound', '0:R|1:Y|2:G|+2:M')
-        # self.value_colorw_widget.set_value_color(
-        #     'multiColorDotsEqualOnUpperBound', '0:R|1:Y|2:G|+2:M')
-
-        # multiColorDotsEqualOnLowerBound
-        # self.value_colorb_widget.set_value_color('multiColorDotsEqualOnLowerBound',
-        #                               '3:R|3.3:Y|=3.3:G')
-        # self.value_colorw_widget.set_value_color('multiColorDotsEqualOnLowerBound',
-        #                               '3:R|3.3:Y|=3.3:G')
-
-
-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 = TestDialog()
-    test.exec_()
-    sys.exit(app.exec_())
diff --git a/sohstationviewer/view/main_window.py b/sohstationviewer/view/main_window.py
index 75caa7184c9f5d78d5a23b782c11ab094234fde9..b7083635be3dddd63c64e1fb1de9603d8f024472 100755
--- a/sohstationviewer/view/main_window.py
+++ b/sohstationviewer/view/main_window.py
@@ -1092,11 +1092,13 @@ class MainWindow(QtWidgets.QMainWindow, UIMainWindow):
         self.pref_soh_list_name = ''
         self.pref_soh_list = []
         self.data_type = 'Unknown'
-        rows = execute_db_dict('SELECT name, IDs, dataType FROM ChannelPrefer '
+        rows = execute_db_dict('SELECT name, preferredSOHs, dataType '
+                               'FROM ChannelPrefer '
                                'WHERE current=1')
         if len(rows) > 0:
             self.pref_soh_list_name = rows[0]['name']
-            self.pref_soh_list = [t.strip() for t in rows[0]['IDs'].split(',')
+            self.pref_soh_list = [t.strip()
+                                  for t in rows[0]['preferredSOHs'].split(',')
                                   if t.strip() != '']
             self.pref_soh_list_data_type = rows[0]['dataType']
 
diff --git a/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py b/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py
index 05d682bb734fad4a61e40248b799f5b7633e2845..4f1fd61dcb877ea077f20ba7edfd197c152ab6d9 100644
--- a/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py
+++ b/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py
@@ -1,6 +1,6 @@
 # Define functions to call processor
 
-from typing import Tuple, Union, Dict, List
+from typing import Tuple, Union, Dict, List, Optional
 
 from PySide6 import QtCore
 
@@ -21,6 +21,9 @@ from sohstationviewer.conf import constants as const
 
 from sohstationviewer.database.extract_data import get_chan_plot_info
 
+from sohstationviewer.view.select_channels_to_show_dialog import \
+    SelectChanelsToShowDialog
+
 
 class MultiThreadedPlottingWidget(PlottingWidget):
     finished = QtCore.Signal()
@@ -30,8 +33,17 @@ class MultiThreadedPlottingWidget(PlottingWidget):
     def __init__(self, *args, **kwargs):
         PlottingWidget.__init__(self, *args, **kwargs)
         self.data_processors: List[PlottingChannelProcessor] = []
-        # pref_order: order of channels to be plotted
+        # total number of channels found
+        self.number_channel_found = 0
+        # order of channels to be plotted
         self.pref_order: List[str] = []
+        # object of data to be plotted
+        self.data_object: Optional[GeneralData] = None
+        # id of data set to be plotted
+        self.data_set_id: Union[str, Tuple[str, str]] = ''
+        self.start_tm: float = 0
+        self.end_tm: float = 0
+
         # Only one data processor can run at a time, so it is not a big problem
         #
         self.thread_pool = QtCore.QThreadPool()
@@ -69,8 +81,8 @@ class MultiThreadedPlottingWidget(PlottingWidget):
         :param time_ticks_total: max number of tick to show on time bar
         :param is_waveform: True if this is a WaveformWidget, otherwise False
         """
+        self.has_data = False
         self.zoom_marker1_shown = False
-        self.data_set_id = data_set_id
         self.processing_log = []  # [(message, type)]
         self.gaps = d_obj.gaps[data_set_id]
         self.gap_bar = None
@@ -80,16 +92,17 @@ class MultiThreadedPlottingWidget(PlottingWidget):
         self.min_x = max(data_time[0], start_tm)
         self.max_x = min(data_time[1], end_tm)
         self.plot_total = len(self.plotting_data1) + len(self.plotting_data2)
-        number_channel_found = len(self.plotting_data1)
+        self.number_channel_found = len(self.plotting_data1)
         name = self.name
         if not is_waveform:
-            number_channel_found += len(self.plotting_data2)
+            self.number_channel_found += len(self.plotting_data2)
             name += " DATA OR MASS POSITION"
-        if number_channel_found == 0:
+        if self.number_channel_found == 0:
             self.title = f"NO {name} DATA TO DISPLAY."
             self.processing_log.append(
                 (f"No {name} data to display.", LogType.INFO))
         else:
+            self.has_data = True
             self.title = get_title(
                 data_set_id, self.min_x, self.max_x, self.date_mode)
         self.plotting_axes.height_normalizing_factor = \
@@ -97,7 +110,7 @@ class MultiThreadedPlottingWidget(PlottingWidget):
         self.plotting_bot = const.BOTTOM
         self.axes = []
 
-        if number_channel_found == 0:
+        if self.number_channel_found == 0:
             # set_size and plot timestamp_bar for the title's position
             self.set_size()
             self.timestamp_bar_top = self.plotting_axes.add_timestamp_bar(
@@ -178,6 +191,8 @@ class MultiThreadedPlottingWidget(PlottingWidget):
         for chan_id in chan_order:
             if 'chan_db_info' not in plotting_data[chan_id]:
                 continue
+            if not plotting_data[chan_id].get('visible'):
+                continue
             channel_processor = PlottingChannelProcessor(
                 plotting_data[chan_id], chan_id,
                 self.min_x, self.max_x
@@ -204,6 +219,11 @@ class MultiThreadedPlottingWidget(PlottingWidget):
         :param time_ticks_total: max number of tick to show on time bar
         :param pref_order: order of channels to be plotted
         """
+        self.data_object = d_obj
+        self.data_set_id = data_set_id
+        self.start_tm = start_tm
+        self.end_tm = end_tm
+        self.time_ticks_total = time_ticks_total
         self.pref_order = pref_order
         if not self.is_working:
             self.reset_widget()
@@ -219,7 +239,7 @@ class MultiThreadedPlottingWidget(PlottingWidget):
                 return
             chan_order1 = self.get_plotting_info(self.plotting_data1, True)
             chan_order2 = self.get_plotting_info(self.plotting_data2)
-
+            self.pref_order = chan_order1
             self.plotting_preset()
 
             self.create_plotting_channel_processors(
@@ -340,3 +360,7 @@ class MultiThreadedPlottingWidget(PlottingWidget):
                               f'{self.name} plot stopped', LogType.INFO)
         self.is_working = False
         self.stopped.emit()
+
+    def select_channels_to_show(self):
+        win = SelectChanelsToShowDialog(self)
+        win.exec()
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting.py b/sohstationviewer/view/plotting/plotting_widget/plotting.py
index 828b15f04ba9c737ea5b04fd8c4ca961b0dc7064..297dff1c940658de52c31d8262ea4e7d0435055b 100644
--- a/sohstationviewer/view/plotting/plotting_widget/plotting.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting.py
@@ -3,13 +3,12 @@ from typing import Dict, Optional
 import numpy as np
 from matplotlib.axes import Axes
 
-from sohstationviewer.controller.util import get_val
 from sohstationviewer.view.plotting.plotting_widget.plotting_helper import (
     get_masspos_value_colors,
     get_categorized_data_from_value_color_equal_on_lower_bound,
     get_categorized_data_from_value_color_equal_on_upper_bound
 )
-from sohstationviewer.view.util.plot_func_names import plot_functions
+from sohstationviewer.view.util.plot_type_info import plot_types
 from sohstationviewer.view.util.color import clr
 from sohstationviewer.view.plotting.plotting_widget.plotting_helper import (
     get_colors_sizes_for_abs_y_from_value_colors, apply_convert_factor
@@ -90,14 +89,14 @@ class Plotting:
                 points, len(points) * [0], linestyle="",
                 marker='s', markersize=2,
                 zorder=constants.Z_ORDER['DOT'],
-                color=clr[c], picker=True, pickradius=3)
+                color=c, picker=True, pickradius=3)
             ax.chan_plots.append(chan_plot)
         total_samples = len(x)
 
         if len(colors) != 1:
-            sample_no_colors = [None, clr['W'], None]
+            sample_no_colors = [None, '#FFFFFF', None]
         else:
-            sample_no_colors = [None, clr[colors[0]], None]
+            sample_no_colors = [None, colors[0], None]
 
         self.plotting_axes.set_axes_info(
             ax, sample_no_list=[None, total_samples, None],
@@ -135,12 +134,10 @@ class Plotting:
         """
         Plot 3 different values in 3 lines with 3 different colors according
         to valueColors:
-            Ex: -1:M|0:R|1:Y  means
-                value = -1  => plot on line y=-1 with M color
-                value = 0   => plot on line y=0 with R color
-                value = 1 => plot on line y=1 with Y color
-        Color codes are defined in colorSettings and limited in 'valColRE'
-            in dbSettings.py
+            Ex: "-1:#FF0000|0:#00FF00|1:#0000FF"  means
+                value = -1  => plot on line y=-1 with #FF0000 color
+                value = 0   => plot on line y=0 with #00FF00 color
+                value = 1 => plot on line y=1 with #0000FF color
 
         :param c_data: data of the channel which includes down-sampled
             (if needed) data in keys 'times' and 'data'.
@@ -161,8 +158,8 @@ class Plotting:
         total_sample_list = []
         for vc in value_colors:
             v, c = vc.split(':')
-            sample_no_colors.append(clr[c])
-            val = get_val(v)
+            sample_no_colors.append(c)
+            val = int(v)
             indexes = np.where(c_data['data'][0] == val)[0]
             times = c_data['times'][0][indexes]
 
@@ -180,7 +177,7 @@ class Plotting:
                 times, len(times) * [val], linestyle="",
                 marker='s', markersize=2,
                 zorder=constants.Z_ORDER['DOT'],
-                color=clr[c], picker=True, pickradius=3)
+                color=c, picker=True, pickradius=3)
             ax.chan_plots.append(dots)
 
             total_sample_list.append(len(times))
@@ -206,10 +203,9 @@ class Plotting:
         """
         Plot channel with 2 different values, one above, one under center line.
         Each value has corresponding color defined in valueColors in database.
-        Ex: 1:Y|0:R  means
-            value == 1 => plot above center line with Y color
-            value == 0 => plot under center line with R color
-        Color codes are defined in colorSettings.
+        Ex: Down:#FF0000|Up:#00FFFF  means
+            value == 1 => plot above center line with #00FFFF color
+            value == 0 => plot under center line with #FF0000 color
 
         :param c_data: data of the channel which includes down-sampled
             (if needed) data in keys 'times' and 'data'.
@@ -227,10 +223,10 @@ class Plotting:
         val_cols = chan_db_info['valueColors'].split('|')
         # up/down has 2 values: 0, 1 which match with index of points_list
         points_list = [[], []]
-        colors = [[], []]
+        colors = {}
         for vc in val_cols:
             v, c = vc.split(':')
-            val = int(get_val(v))
+            val = 0 if v == 'Down' else 1
             points = []
             for times, data in zip(c_data['times'], c_data['data']):
                 points += [times[i]
@@ -243,20 +239,20 @@ class Plotting:
         down_dots, = ax.plot(
             points_list[0], len(points_list[0]) * [-0.5], linestyle="",
             marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'],
-            color=clr[colors[0]], picker=True, pickradius=3)
+            color=colors[0], picker=True, pickradius=3)
         ax.chan_plots.append(down_dots)
         # up dots
         up_dots, = ax.plot(
             points_list[1], len(points_list[1]) * [0.5], linestyle="",
             marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'],
-            color=clr[colors[1]], picker=True, pickradius=3)
+            color=colors[1], picker=True, pickradius=3)
         ax.chan_plots.append(up_dots)
 
         ax.set_ylim(-2, 2)
         self.plotting_axes.set_axes_info(
             ax,
             sample_no_list=[len(points_list[0]), None, len(points_list[1])],
-            sample_no_colors=[clr[colors[0]], None, clr[colors[1]]],
+            sample_no_colors=[colors[0], None, colors[1]],
             sample_no_pos=[0.25, None, 0.75],
             chan_db_info=chan_db_info)
 
@@ -268,11 +264,14 @@ class Plotting:
         ax.chan_db_info = chan_db_info
         return ax
 
-    def plot_time_dots(
+    def plot_dot_for_time(
             self, c_data: Dict, chan_db_info: Dict,
             chan_id: str, ax: Optional[Axes] = None) -> Axes:
         """
-        Plot times only
+        Plot a dot at each time point in the data. The color of the dot is
+        specified in the valueColors
+        Ex: Color:#00FF00 means the dot will have the color #00FF00
+
         :param c_data: dict - data of the channel which includes down-sampled
             data in keys 'times' and 'data'. Refer to DataTypeModel.__init__.
             soh_data[data_set_id][chan_id]
@@ -289,14 +288,15 @@ class Plotting:
             ax = self.plotting_axes.create_axes(
                 self.parent.plotting_bot, plot_h)
 
-        color = 'W'
+        # Set the color to white by default
+        color = '#000000'
         if chan_db_info['valueColors'] not in [None, 'None', '']:
-            color = chan_db_info['valueColors'].strip()
+            color = chan_db_info['valueColors'].strip()[6:]
         x_list = c_data['times']
         total_x = sum([len(x) for x in x_list])
         self.plotting_axes.set_axes_info(
             ax, sample_no_list=[None, total_x, None],
-            sample_no_colors=[None, clr[color], None],
+            sample_no_colors=[None, color, None],
             sample_no_pos=[None, 0.5, None],
             chan_db_info=chan_db_info)
 
@@ -304,7 +304,7 @@ class Plotting:
             chan_plot, = ax.plot(
                 x, [0] * len(x), marker='s', markersize=1.5,
                 linestyle='', zorder=constants.Z_ORDER['LINE'],
-                color=clr[color], picker=True,
+                color=color, picker=True,
                 pickradius=3)
             ax.chan_plots.append(chan_plot)
         ax.x_center = x_list[0]
@@ -318,13 +318,15 @@ class Plotting:
         """
         Plot lines with dots at the data points. Colors of dot and lines are
         defined in valueColors in database.
-        Ex: L:G|D:W|Z:C  means
-            Lines are plotted with color G
-            Dots are plotted with color W
-            Additional dot with value Zero in color C (for channel GPS Lk/Unlk)
-        If D is not defined, dots won't be displayed.
-        If L is not defined, lines will be plotted with color G
-        Color codes are defined in colorSettings
+        Ex: Line:#00FF00|Dot:#FF0000|Zero:#0000FF  means
+            Lines are plotted with color #00FF00
+            Dots are plotted with color #FF0000
+            Additional dot with value Zero in color #0000FF (for channel GPS
+            Lk/Unlk)
+        If Dot is not defined, dots won't be displayed.
+        If Line is not defined, lines will be plotted with color G.
+        If Zero is not defined, points with value zero will be plotted the same
+            as other points.
 
         :param c_data: data of the channel which includes down-sampled
             (if needed) data in keys 'times' and 'data'.
@@ -349,18 +351,18 @@ class Plotting:
             for cStr in color_parts:
                 obj, c = cStr.split(':')
                 colors[obj] = c
-        l_color = 'G'
+        l_color = '#00FF00'
         has_dot = False
-        if 'L' in colors:
-            l_color = colors['L']
-        if 'D' in colors:
-            d_color = colors['D']
+        if 'Line' in colors:
+            l_color = colors['Line']
+        if 'Dot' in colors:
+            d_color = colors['Dot']
             has_dot = True
         else:
             d_color = l_color
 
         if chan_id == 'GPS Lk/Unlk':
-            z_color = colors['Z']
+            z_color = colors['Zero']
             sample_no_list = []
             ax.x_bottom = x_list[0][np.where(y_list[0] == -1)[0]]
             sample_no_list.append(ax.x_bottom.size)
@@ -368,7 +370,7 @@ class Plotting:
             sample_no_list.append(ax.x_center.size)
             ax.x_top = x_list[0][np.where(y_list[0] == 1)[0]]
             sample_no_list.append(ax.x_top.size)
-            sample_no_colors = [clr[d_color], clr[z_color], clr[d_color]]
+            sample_no_colors = [d_color, z_color, d_color]
             sample_no_pos = [0.05, 0.5, 0.95]
             top_bottom_index = np.where(y_list[0] != 0)[0]
 
@@ -382,15 +384,15 @@ class Plotting:
                 markersize=1.5,
                 linestyle='',
                 zorder=constants.Z_ORDER['DOT'],
-                mfc=clr[z_color],
-                mec=clr[z_color],
+                mfc=z_color,
+                mec=z_color,
                 picker=True, pickradius=3)
             ax.chan_plots.append(chan_plot)
 
             info = "GPS Clock Power"
         else:
             sample_no_list = [None, sum([len(x) for x in x_list]), None]
-            sample_no_colors = [None, clr[d_color], None]
+            sample_no_colors = [None, d_color, None]
             sample_no_pos = [None, 0.5, None]
             ax.x_center = x_list[0]
             ax.y_center = y_list[0]
@@ -412,16 +414,16 @@ class Plotting:
                     x, y, marker='o', markersize=0.01,
                     linestyle='-', linewidth=0.7,
                     zorder=constants.Z_ORDER['LINE'],
-                    color=clr[l_color],
+                    color=l_color,
                     picker=True, pickradius=3)
             else:
                 chan_plot, = ax.plot(
                     x, y, marker='s', markersize=1.5,
                     linestyle='-', linewidth=0.7,
                     zorder=constants.Z_ORDER['LINE'],
-                    color=clr[l_color],
-                    mfc=clr[d_color],
-                    mec=clr[d_color],
+                    color=l_color,
+                    mfc=d_color,
+                    mec=d_color,
                     picker=True, pickradius=3)
             ax.chan_plots.append(chan_plot)
 
@@ -499,7 +501,7 @@ class Plotting:
             # should be treated in a different way if want to replot.
         ax.x_center = x_list[0]
         ax.y_center = apply_convert_factor(
-            y_list, chan_id, self.main_window.data_type)
+            y_list, chan_id, self.main_window.data_type)[0]
         ax.chan_db_info = chan_db_info
         return ax
 
@@ -551,6 +553,6 @@ class Plotting:
             # when edit select a param that has no plot type
             return self.plot_no_plot_type(c_data, chan_db_info, chan_id, ax)
         ax = getattr(
-            self, plot_functions[plot_type]['plot_function'])(
+            self, plot_types[plot_type]['plot_function'])(
             c_data, chan_db_info, chan_id, ax)
         return ax
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py b/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py
index 449633141ef57b3dc7ce1173e574b4f6f91aa480..49299a2b14f293a6259af3b0cbe927e9e8b38cce 100644
--- a/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting_helper.py
@@ -75,13 +75,10 @@ def get_categorized_data_from_value_color_equal_on_upper_bound(
     Separate data points and color using valueColors in which the condition
     will check if value equal to or less than upper bound and greater than
     lower bound as following example
-        0:R|2.3:Y|+2.3:G  means
-            + value <= 0   => plot with R color
-            + 0 < value <= 2.3 => plot with Y color
-            + 2.3 < value  => plot with G color
-    Beside additional signs are included:
-        Ex: *:W  means everything with white color
-        Ex: -1:_ means not plotting value <= -1
+        <=-1:not plot|<=0:#FF0000|0<:#FF00FF means:
+           value <= -1   => not plot
+           -1 < value <= 0 => plot with #FF0000 color
+           0 < value  => plot with #FF00FF color
     :param c_data: dict of data of the channel which includes down-sampled
         data in keys 'times' and 'data'.
     :param chan_db_info: dict of info of channel from DB
@@ -99,7 +96,7 @@ def get_categorized_data_from_value_color_equal_on_upper_bound(
             val = v         # to have some value for pre_val = val
         else:
             val = get_val(v)
-        if c == '_':
+        if c == 'not plot':
             prev_val = val
             continue
         colors.append(c)
@@ -108,10 +105,11 @@ def get_categorized_data_from_value_color_equal_on_upper_bound(
         # element (c_data['times'][0]).
         # Same for c_data['data']
         times, data = c_data['times'][0], c_data['data'][0]
-        if v.startswith('+'):
+        if v[-1] == '<':
+            # Deal with the last value colors section.
             points = [times[i]
                       for i in range(len(data))
-                      if data[i] > val]
+                      if val < data[i]]
         elif v == '*':
             points = times
         else:
@@ -130,10 +128,10 @@ def get_categorized_data_from_value_color_equal_on_lower_bound(
     Separate data points and color using valueColors in which the condition
     will check if value equal to or greater than lower bound and less than
     upper bound as the following example:
-        3.:R|3.3:Y|=3.3:G  means
-            + value < 3.   => plot with R color
-            + 3<= value < 3.3 => plot with Y color
-            + value = 3.3  => plot with G color
+        <-1:not plot|<0:#FF0000|=0:#FF00FF  means:
+            value < -1   => not plot
+            -1 =< value < 0 => plot with #FF0000 color
+            value >= 0  => plot with #FF00FF color
     :param c_data: dict of data of the channel which includes down-sampled
         data in keys 'times' and 'data'.
     :param chan_db_info: dict of info of channel from DB
@@ -151,6 +149,7 @@ def get_categorized_data_from_value_color_equal_on_lower_bound(
         val = get_val(v)
         times, data = c_data['times'][0], c_data['data'][0]
         if v.startswith('='):
+            # Deal with the last value colors section.
             points = [times[i]
                       for i in range(len(data))
                       if data[i] >= val]
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
index b2911012e8b6ba57426aea9c32300a96b5f65de6..392159a4db3db51db8ca04c146e3ae4543e942ce 100644
--- a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
@@ -25,7 +25,8 @@ from sohstationviewer.view.save_plot_dialog import SavePlotDialog
 from sohstationviewer.controller.plotting_data import (
     format_time, get_disk_size_format
 )
-from sohstationviewer.controller.util import display_tracking_info
+from sohstationviewer.controller.util import \
+    display_tracking_info, get_formatted_time_delta
 
 
 class PlottingWidget(QtWidgets.QScrollArea):
@@ -52,6 +53,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
         self.tracking_box = tracking_box
         # =============== declare attributes =======================
         """
+        has_data: indicate if there're any data to be plotted
+        """
+        self.has_data: bool = False
+        """
         processing_log: [(str,str] - list of processing info and type
         """
         self.processing_log = []
@@ -97,6 +102,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
         """
         self.gap_bar = None
         """
+        gaps: list of gaps of the displayed data set
+        """
+        self.gaps: List[List[float, float]] = []
+        """
         timestamp_bar_top: matplotlib.axes.Axes - axes to show times that
             places at the top of all channel plots
         timestamp_bar_bottom: matplotlib.axes.Axes - axes to show times that
@@ -174,6 +183,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
 
         QtWidgets.QScrollArea.__init__(self)
 
+        # To avoid artifact show up at the scrollbar position between the
+        # plotting of available and unavailable scrollbar, it is set
+        # to be always on for vertical and off for horizontal
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
         self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
         """
         main_widget: QWidget - widget in the scroll area to keep the drawing
@@ -241,10 +254,9 @@ class PlottingWidget(QtWidgets.QScrollArea):
             view_port_size = self.maximumViewportSize()
         view_port_width = view_port_size.width()
         self.plotting_axes.canvas.setFixedWidth(view_port_width)
-        fig_width_in = view_port_width/self.parent.dpi_x
+        self.fig_width_in = view_port_width/self.parent.dpi_x
 
         self.plotting_axes.fig.set_dpi(self.parent.actual_dpi)
-        self.plotting_axes.fig.set_figwidth(fig_width_in)
 
         # set view size fit with the scroll's viewport size
         self.main_widget.setFixedWidth(view_port_size.width())
@@ -272,6 +284,13 @@ class PlottingWidget(QtWidgets.QScrollArea):
         max_height = max(self.maximumViewportSize().height(), fig_height)
         self.main_widget.setFixedHeight(max_height)
         self.plotting_axes.canvas.setFixedHeight(max_height)
+        # If figure width is set before canvas height is set, it will be
+        # changed once canvas height is set for the first plotting. But in the
+        # second plotting, that problem won't happen so the figure width remain
+        # the same as what is set. As the result the figure width will be
+        # different between the first plotting and the rest.
+        # To avoid that figure's width has to be set after canvas' height.
+        self.plotting_axes.fig.set_figwidth(self.fig_width_in)
         # calculate height_normalizing_factor which is the ratio to multiply
         # with height ratio of an item to fit in figure height start from
         # 1 to 0.
@@ -356,6 +375,30 @@ class PlottingWidget(QtWidgets.QScrollArea):
         else:
             return super().eventFilter(target, event)
 
+    def display_gap_info(self, gap_rectangle: pl.Rectangle) -> None:
+        """
+        Display gap info in tracking box
+        :param gap_rectangle: rectangle that displays the clicked gap on figure
+        """
+        gap_start = gap_rectangle.get_x()
+        gap_width = gap_rectangle.get_width()
+        gap_end = gap_start + gap_width
+
+        all_gap_starts = list(zip(*self.gaps))[0]
+        gap_position = all_gap_starts.index(gap_start) + 1
+        fortmat_start_time = format_time(
+            gap_start, self.date_mode, 'HH:MM:SS')
+        fortmat_end_time = format_time(
+            gap_end, self.date_mode, 'HH:MM:SS')
+
+        gap_delta = get_formatted_time_delta(gap_width)
+        info_str = (f"Gap {gap_position}:  "
+                    f"{fortmat_start_time}  to  "
+                    f"{fortmat_end_time}  "
+                    f"{gap_delta}")
+        info_str = "<pre>" + info_str + "</pre>"
+        display_tracking_info(self.tracking_box, info_str)
+
     def on_pick_event(self, event):
         """
         When click mouse on a clickable data point (dot with picker=True in
@@ -367,6 +410,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
         self.is_button_press_event_triggered_pick_event = True
         artist = event.artist
         self.log_idxes = None
+
+        if isinstance(artist, pl.Rectangle):
+            return self.display_gap_info(artist)
+
         if not isinstance(artist, pl.Line2D):
             return
         ax = artist.axes
@@ -431,6 +478,8 @@ class PlottingWidget(QtWidgets.QScrollArea):
         :param event: button_press_event - mouse click event
         """
         for w in self.peer_plotting_widgets:
+            if not w.has_data:
+                continue
             if w.is_working:
                 # try to zoom or use ruler while not done plotting
                 return
@@ -460,6 +509,8 @@ class PlottingWidget(QtWidgets.QScrollArea):
                 pass
 
         for w in self.peer_plotting_widgets:
+            if not w.has_data:
+                continue
             if modifiers in [QtCore.Qt.KeyboardModifier.ControlModifier,
                              QtCore.Qt.KeyboardModifier.MetaModifier,
                              QtCore.Qt.KeyboardModifier.ShiftModifier]:
@@ -570,6 +621,8 @@ class PlottingWidget(QtWidgets.QScrollArea):
             except AttributeError:
                 pass
             for w in self.peer_plotting_widgets:
+                if not w.has_data:
+                    continue
                 w.set_rulers_invisible()
                 w.zoom_marker1_shown = False
                 try:
@@ -650,7 +703,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                         new_x_top_indexes.size)
                 if hasattr(ax, 'x_center'):
                     if not hasattr(ax, 'y_center'):
-                        # plot_time_dots and plot_multi_color_dots(_xxx)
+                        # plot_dot_for_time and plot_multi_color_dots(_xxx)
                         x = ax.x_center
                         new_x_indexes = np.where(
                             (x >= self.min_x) & (x <= self.max_x))[0]
diff --git a/sohstationviewer/view/plotting/state_of_health_widget.py b/sohstationviewer/view/plotting/state_of_health_widget.py
index f9743033a895c51540829187fd090f2c8d8a9062..965818e1d8dc3cd3900bb4ac904968269abab722 100644
--- a/sohstationviewer/view/plotting/state_of_health_widget.py
+++ b/sohstationviewer/view/plotting/state_of_health_widget.py
@@ -65,6 +65,15 @@ class SOHWidget(MultiThreadedPlottingWidget):
         """
         Create menu showing up when right click mouse to add/edit channel
         """
+        if self.number_channel_found == 0:
+            return
+        context_menu = QtWidgets.QMenu(self)
+
+        select_channels_to_show_action = context_menu.addAction(
+            "Select Channels to show")
+        select_channels_to_show_action.triggered.connect(
+            self.select_channels_to_show)
+
         try:
             if '?' in self.curr_ax.chan_db_info['dbChannel']:
                 warning_action_str = (
@@ -78,9 +87,8 @@ class SOHWidget(MultiThreadedPlottingWidget):
             else:
                 add_edit_action_str = f"Edit channel {self.curr_ax.chan}"
         except AttributeError:
-            return
+            pass
 
-        context_menu = QtWidgets.QMenu(self)
         try:
             add_edit_chan_action = context_menu.addAction(add_edit_action_str)
             add_edit_chan_action.triggered.connect(self.add_edit_channel)
@@ -93,6 +101,7 @@ class SOHWidget(MultiThreadedPlottingWidget):
 
         context_menu.exec_(self.mapToGlobal(event.pos()))
         self.curr_ax = None         # to make sure curr_ax is clear
+        return super(SOHWidget, self).contextMenuEvent(event)
 
     def init_plot(self, d_obj: GeneralData,
                   data_set_id: Union[str, Tuple[str, str]],
@@ -106,7 +115,6 @@ class SOHWidget(MultiThreadedPlottingWidget):
         :param end_tm: requested end time to read
         :param time_ticks_total: max number of tick to show on time bar
         """
-        self.data_object = d_obj
         self.plotting_data1 = (
             d_obj.soh_data[data_set_id] if data_set_id else {})
         self.plotting_data2 = (
diff --git a/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py b/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py
index fc23dc2853e079d8c36657778fcb15ccb4420b09..24c6029f38feced995005fcdc737862032827b13 100755
--- a/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py
+++ b/sohstationviewer/view/plotting/time_power_square/time_power_squared_dialog.py
@@ -51,6 +51,12 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
          date_format: format for date
         """
         self.date_format: str = 'YYYY-MM-DD'
+        """
+        min_x: left limit of data plotted
+        max_x: right limit of data plotted
+        """
+        self.min_x: float = 0
+        self.max_x: float = 0
 
         self.setWindowTitle("TPS Plot")
 
@@ -294,10 +300,10 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
         :param end_tm: requested end time to read
         """
         self.processing_log_msg = ""
-        min_x = max(d_obj.data_time[data_set_id][0], start_tm)
-        max_x = min(d_obj.data_time[data_set_id][1], end_tm)
+        self.min_x = max(d_obj.data_time[data_set_id][0], start_tm)
+        self.max_x = min(d_obj.data_time[data_set_id][1], end_tm)
         self.start_5mins_of_diff_days = get_start_5mins_of_diff_days(
-            min_x, max_x)
+            self.min_x, self.max_x)
         for i in range(self.plotting_tab.count() - 1, -1, -1):
             # delete all tps tabs
             widget = self.plotting_tab.widget(i)
@@ -338,4 +344,5 @@ class TimePowerSquaredDialog(QtWidgets.QWidget):
         self.plotting_tab.addTab(tps_widget, tab_name)
         self.tps_widget_dict[tab_name] = tps_widget
         tps_widget.plot_channels(
-            data_dict, data_set_id, self.start_5mins_of_diff_days)
+            data_dict, data_set_id, self.start_5mins_of_diff_days,
+            self.min_x, self.max_x)
diff --git a/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py b/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py
index 556db8f625a42fd70e2406201e437abff657479e..1e910d0928e0e254c6d67c1a36ccc16966d6ed52 100644
--- a/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py
+++ b/sohstationviewer/view/plotting/time_power_square/time_power_squared_widget.py
@@ -102,17 +102,23 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
 
     def plot_channels(self, data_dict: Dict,
                       data_set_id: Union[str, Tuple[str, str]],
-                      start_5mins_of_diff_days: List[List[float]]):
+                      start_5mins_of_diff_days: List[List[float]],
+                      min_x: float, max_x: float):
         """
         Recursively plot each TPS channels for waveform_data.
         :param data_dict: dict of all channels to be plotted
         :param data_set_id: data set's id
         :param start_5mins_of_diff_days: the list of starts of all five minutes
             of days in which each day has 288 of 5 minutes.
+        :param min_x: left limit of data plotted
+        :param max_x: right limit of data plotted
         """
+        self.has_data = False
         self.zoom_marker1_shown = False
         self.is_working = True
         self.plotting_data1 = data_dict
+        self.min_x = min_x
+        self.max_x = max_x
         self.plot_total = len(self.plotting_data1)
         self.fig_height_in = self.init_fig_height_in()
         self.start_5mins_of_diff_days = start_5mins_of_diff_days
@@ -132,6 +138,7 @@ class TimePowerSquaredWidget(plotting_widget.PlottingWidget):
             self.processing_log.append(
                 ("No WAVEFORM data to display TPS.", LogType.INFO))
         else:
+            self.has_data = True
             self.title = get_title(
                 data_set_id, self.min_x, self.max_x, self.date_mode)
         self.plotting_axes.height_normalizing_factor = \
diff --git a/sohstationviewer/view/plotting/waveform_dialog.py b/sohstationviewer/view/plotting/waveform_dialog.py
index ca0c51ee5b2110da7592947b30d66df5fbdc8880..c0a4ca31e370b8c773a9832e159ad651bcff261a 100755
--- a/sohstationviewer/view/plotting/waveform_dialog.py
+++ b/sohstationviewer/view/plotting/waveform_dialog.py
@@ -16,6 +16,22 @@ class WaveformWidget(MultiThreadedPlottingWidget):
     def __init__(self, *args, **kwargs):
         MultiThreadedPlottingWidget.__init__(self, *args, **kwargs)
 
+    def contextMenuEvent(self, event):
+        """
+        Create menu showing up when right click mouse to add/edit channel
+        """
+        if self.number_channel_found == 0:
+            return
+        context_menu = QtWidgets.QMenu(self)
+        select_channels_to_show_action = context_menu.addAction(
+            "Select Channels to show")
+        select_channels_to_show_action.triggered.connect(
+            self.select_channels_to_show)
+
+        context_menu.exec_(self.mapToGlobal(event.pos()))
+        self.curr_ax = None         # to make sure curr_ax is clear
+        return super(WaveformWidget, self).contextMenuEvent(event)
+
     def init_plot(self, d_obj: GeneralData,
                   data_set_id: Union[str, Tuple[str, str]],
                   start_tm: float, end_tm: float, time_ticks_total: int):
@@ -136,6 +152,7 @@ class WaveformDialog(QtWidgets.QWidget):
         :param event: QResizeEvent - resize event
         """
         self.plotting_widget.init_size()
+        return super(WaveformDialog, self).resizeEvent(event)
 
     def moveEvent(self, event):
         """
diff --git a/sohstationviewer/view/select_channels_to_show_dialog.py b/sohstationviewer/view/select_channels_to_show_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e1cf3ebf04cb6ecb5ed0919e2162a47ad88afbe
--- /dev/null
+++ b/sohstationviewer/view/select_channels_to_show_dialog.py
@@ -0,0 +1,155 @@
+from typing import Dict, List
+
+from PySide6 import QtWidgets, QtCore
+from PySide6.QtWidgets import (
+    QDialog, QListWidget, QListWidgetItem, QAbstractItemView, QLabel,
+    QPushButton
+)
+from PySide6.QtCore import Qt
+
+
+class SelectChanelsToShowDialog(QDialog):
+    def __init__(self, parent):
+        """
+        Dialog to select channels to display and reorder them by dragging and
+        dropping list items.
+
+        :param parent: the parent widget
+        """
+        super(SelectChanelsToShowDialog, self).__init__()
+        self.parent = parent
+
+        # reorderablable_chan_list_widget: list widget where channels can be
+        # reordered by dragging and dropping, used to display SOH and waveform
+        # channels
+        self.reorderable_chan_list_widget = self.create_chan_list_widget(
+            self.parent.plotting_data1, self.parent.pref_order, True
+        )
+
+        # unreorderable_chan_list_widget: list widget where channels can NOT be
+        # reordered, used to display mass position channels
+        self.unreorderable_chan_list_widget = self.create_chan_list_widget(
+            self.parent.plotting_data2, [], False
+        )
+
+        self.cancel_btn = QPushButton('CANCEL', self)
+        self.apply_btn = QPushButton('APPLY', self)
+
+        self.setup_ui()
+        self.connect_signals()
+
+    @staticmethod
+    def create_chan_list_widget(channel_dict: Dict,
+                                pref_order: List[str],
+                                reorderable: bool):
+        """
+        Create a checkbox for each channel in channel_dict with the order
+        according to pref_order if any.
+
+        :param channel_dict: dictionary of channel data
+        :param pref_order: list of preferred order of channels to be plotted
+        :param reorderable: flag for chan_list to be able to drag and drop for
+            reordering
+        """
+        chan_list_widget = QListWidget()
+        chan_order = channel_dict.keys()
+        if pref_order:
+            chan_order = pref_order
+
+        for chan_id in chan_order:
+            if chan_id not in channel_dict.keys():
+                continue
+            chan_item = QListWidgetItem(chan_id)
+            if channel_dict[chan_id]['visible']:
+                chan_item.setCheckState(Qt.CheckState.Checked)
+            else:
+                chan_item.setCheckState(Qt.CheckState.Unchecked)
+            chan_list_widget.addItem(chan_item)
+        if reorderable:
+            chan_list_widget.setDragDropMode(
+                QAbstractItemView.DragDropMode.InternalMove)
+
+        # set height fit to content's height, not use scroll bar
+        chan_list_widget.verticalScrollBar().setDisabled(True)
+        chan_list_widget.setHorizontalScrollBarPolicy(
+            QtCore.Qt.ScrollBarAlwaysOff)
+        chan_list_widget.setVerticalScrollBarPolicy(
+            QtCore.Qt.ScrollBarAlwaysOff)
+        chan_list_widget.setFixedHeight(
+            chan_list_widget.count() * chan_list_widget.sizeHintForRow(0)
+        )
+
+        return chan_list_widget
+
+    def setup_ui(self) -> None:
+        self.setWindowTitle("Show/Hide and Reorder Channels")
+
+        main_layout = QtWidgets.QVBoxLayout()
+        self.setLayout(main_layout)
+
+        label = QLabel(
+            "* Check to show/ uncheck to hide a channel.\n\n"
+            "* Drop and drag channels in the first list to reorder.")
+        main_layout.addWidget(label)
+        main_layout.addWidget(self.reorderable_chan_list_widget)
+
+        label = QLabel("* Mass Position channels won't be reorderable.")
+        main_layout.addWidget(label)
+        main_layout.addWidget(self.unreorderable_chan_list_widget)
+
+        button_layout = QtWidgets.QHBoxLayout()
+        main_layout.addLayout(button_layout)
+        button_layout.addWidget(self.cancel_btn)
+        button_layout.addWidget(self.apply_btn)
+
+    def connect_signals(self) -> None:
+        self.cancel_btn.clicked.connect(self.close)
+        self.apply_btn.clicked.connect(self.apply)
+
+    @staticmethod
+    def apply_selection(
+            channel_list_widget, plotting_data):
+        """
+        Set show value according to channel checkboxes' check state
+        :param channel_list_widget: the list widget that contains checkboxes
+            representing channels' show status
+        :param plotting_data: the channel dota in which channels have item show
+            to be modified.
+        :return new_pref_order: the new preferred order that channels are going
+            to be plotted according to.
+        """
+        new_pref_order = []
+        for row in range(channel_list_widget.count()):
+            chan_item = channel_list_widget.item(row)
+            chan_id = chan_item.text()
+            if chan_item.checkState() == Qt.CheckState.Checked:
+                plotting_data[chan_id]['visible'] = True
+            else:
+                plotting_data[chan_id]['visible'] = False
+            new_pref_order.append(chan_id)
+        return new_pref_order
+
+    @QtCore.Slot()
+    def apply(self) -> None:
+        """
+        Change value of key 'visible' according to the selections then replot
+        channels in new order for plotting_data1 in parent widget
+        """
+
+        new_pref_order = (
+            self.apply_selection(
+                self.reorderable_chan_list_widget, self.parent.plotting_data1))
+        self.apply_selection(
+            self.unreorderable_chan_list_widget, self.parent.plotting_data2)
+
+        self.parent.clear()
+        self.parent.plot_channels(
+            self.parent.data_object,
+            self.parent.data_set_id,
+            self.parent.start_tm,
+            self.parent.end_tm,
+            self.parent.time_ticks_total,
+            new_pref_order)
+        self.parent.draw()
+
+        self.close()
diff --git a/sohstationviewer/view/ui/main_ui.py b/sohstationviewer/view/ui/main_ui.py
index d0ed6e746a20ea490ae9705d9151f5d17fbefd3c..ce67588d388dd12648f572355e7081ef46bc7555 100755
--- a/sohstationviewer/view/ui/main_ui.py
+++ b/sohstationviewer/view/ui/main_ui.py
@@ -165,11 +165,12 @@ class UIMainWindow(object):
         self.all_soh_chans_check_box: Union[QCheckBox, None] = None
         """
         select_pref_soh_list_button: Button to open self.channel_prefer_dialog
-            to view/add/edit/select a preferred IDs
+            to view/add/edit/select a preferred SOH IDs
         """
         self.select_pref_soh_list_button: Union[QPushButton, None] = None
         """
-        curr_pref_soh_list_name_txtbox: textbox to display name of current IDs.
+        curr_pref_soh_list_name_txtbox: textbox to display name of current
+            preferred SOH IDs.
             When all_soh_chans_check_box is checked, no name show up, when
             all_soh_chans_check_box is unchecked, current selected name show up
         """
@@ -375,9 +376,6 @@ class UIMainWindow(object):
                                          self.tracking_info_text_browser,
                                          'SOH',
                                          self.main_window)
-        # So the scrollbar won't show up when pulling up the splitter
-        self.plotting_widget.setHorizontalScrollBarPolicy(
-            QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
 
         plot_splitter.addWidget(self.plotting_widget)
 
diff --git a/sohstationviewer/view/util/plot_func_names.py b/sohstationviewer/view/util/plot_func_names.py
deleted file mode 100644
index 236917c658d559734d030ae51b4c663a62b1ea51..0000000000000000000000000000000000000000
--- a/sohstationviewer/view/util/plot_func_names.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import re
-
-plot_functions = {
-        'linesDots': {
-            "description": (
-                "Lines, one color dots. Dot or line/color mapping defined by "
-                "ValueColors with available colors: RYGMCW.\n"
-                "Ex: L:G|D:W\n"
-                "  means \tLines are plotted with color G\n"
-                "\tDots are plotted with color W\n"
-                "If D is not defined, dots won't be displayed.\n"
-                "If L is not defined, lines will be plotted with color G"),
-            "plot_function": "plot_lines_dots",
-            "value_pattern": re.compile('^(L|D|Line|Dot)$')
-        },
-        'linesSRate': {
-            "description": "Lines, one color dots, bitweight info. ",
-            "plot_function": "plot_lines_s_rate"
-        },
-        'linesMasspos': {
-            "description": "multi-line mass position, multi-color dots. ",
-            "plot_function": "plot_lines_mass_pos"
-        },
-        'triColorLines': {
-            "description": "Three lines with three different colors for "
-                           "values -1, 0, 1.",
-            "plot_function": "plot_tri_colors",
-            "value_pattern": re.compile('^-?[10]?$')
-        },
-        'dotForTime': {
-            "description": (
-                "Dots according to timestamp.\n"
-                "Color defined by ValueColors with available colors: RYGMCW.\n"
-                "Ex: G"),
-            "plot_function": "plot_time_dots"
-        },
-        'multiColorDotsEqualOnUpperBound': {
-            "description": (
-                "Multicolor dots in center with value/colors mapping defined "
-                "by ValueColors with available colors: RYGMCW.\n"
-                "Value from low to high.\n"
-                "Ex: *:W  in which all values represent by white dots.\n"
-                "Ex: -1:_|0:R|2.3:Y|+2.3:G\n"
-                "  in which \tvalue <= -1  => not plot\n"
-                "\tvalue <= 0   => plot with R color\n"
-                "\t0 < value <= 2.3 => plot with Y color\n"
-                "\t2.3 < value  => plot with G color\n"),
-            "plot_function": "plot_multi_color_dots_equal_on_upper_bound",
-            "value_pattern": re.compile('^(\+|<=)?[0-9]+\.?[0-9]?<?$')  # noqa: W605,E501
-        },
-        'multiColorDotsEqualOnLowerBound': {
-            "description": (
-                "Multicolor dots in center with value/colors mapping defined "
-                "by ValueColors with available colors: RYGMCW.\n"
-                "Value from low to high.\n"
-                "Ex: 3.:R|3.3:Y|=3.3:G\n"
-                "  in which"
-                "\tvalue < 3.   => plot with R color\n"
-                "\t3. <= value < 3.3 => plot with Y color\n"
-                "\tvalue = 3.3  => plot with G color\n"),
-            "plot_function": "plot_multi_color_dots_equal_on_lower_bound",
-            "value_pattern": re.compile('^[=<]?[0-9]\.?[0-9]?$')  # noqa: W605
-        },
-        'upDownDots': {
-            "description": (
-                "Show data with 2 different values: first down/ second up. "
-                "Colors defined by ValueColors.\nEx: 1:R|0:Y"),
-            "plot_function": 'plot_up_down_dots',
-            "value_pattern": re.compile("^(0|1|Up|Down)$")
-        }
-    }
diff --git a/sohstationviewer/view/util/plot_type_info.py b/sohstationviewer/view/util/plot_type_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d2f8c9b90040fe89738f7d5bbc525c53a1db25f
--- /dev/null
+++ b/sohstationviewer/view/util/plot_type_info.py
@@ -0,0 +1,122 @@
+import re
+
+color_regex = '#[0-9A-F]{6}'
+
+plot_types = {
+        'linesDots': {
+            "description": (
+                "Lines, one color dots. Dot or line/color mapping defined by "
+                "ValueColors.\n"
+                "Ex: Line:#00FF00|Dot:#FF0000  means\n"
+                "   Lines are plotted with color #00FF00\n"
+                "   Dots are plotted with color #FF0000\n"
+                "If Dot is not defined, dots won't be displayed.\n"
+                "If L is not defined, lines will be plotted with color "
+                "#00FF00.\n"
+                "Optionally, a color for points with value 0 can be defined "
+                "This is currently only used for channel GPS Lk/Unlk.\n"
+                "Ex: Zero:#0000FF means points with value are plotted with "
+                "color #0000FF."
+            ),
+            "plot_function": "plot_lines_dots",
+            "value_pattern": re.compile('^(L|D|Z|Line|Dot|Zero)$'),
+            "pattern": re.compile(f'^(?:Line|Dot|Zero):{color_regex}$'),
+            "instruction": (
+                "Ex: Line:#00FF00|Dot:#FF0000  means\n"
+                "   Lines are plotted with color #00FF00\n"
+                "   Dots are plotted with color #FF0000\n"
+                "If Dot is not defined, dots won't be displayed.\n"
+                "If L is not defined, lines will be plotted with color "
+                "#00FF00.\n"
+                "Optionally, a color for points with value 0 can be defined "
+                "This is currently only used for channel GPS Lk/Unlk.\n"
+                "Ex: Zero:#0000FF means points with value are plotted with "
+                "color #0000FF."
+            ),
+            "default_value_color": "Line:#00FF00"
+        },
+        'linesSRate': {
+            "description": "Lines, one color dots, bitweight info. ",
+            "plot_function": "plot_lines_s_rate"
+        },
+        'linesMasspos': {
+            "description": "multi-line mass position, multi-color dots. ",
+            "plot_function": "plot_lines_mass_pos"
+        },
+        'triColorLines': {
+            "description": "Three lines with three different colors for "
+                           "values -1, 0, 1.",
+            "plot_function": "plot_tri_colors",
+            "value_pattern": re.compile('^-?[10]?$'),
+            "pattern": re.compile(f'^-?[10]:{color_regex}$'),
+            "default_value_color": "-1:#FF0000|0:#00FF00|1:#0000FF"
+        },
+        'dotForTime': {
+            "description": (
+                "Dots according to timestamp.\n"
+                "Color defined by ValueColors.\n"
+                "Ex: Color:#00FF00"),
+            "plot_function": "plot_dot_for_time",
+            "value_pattern": re.compile('^C|(Color)$'),
+            "pattern": re.compile(f'^Color:{color_regex}$'),
+            "default_value_color": "Color:#00FF00"
+        },
+        'multiColorDotsEqualOnUpperBound': {
+            "description": (
+                "Multicolor dots in center with value/colors mapping defined "
+                "by ValueColors.\n"
+                "Ex: <=-1:not plot|<=0:#FF0000|0<:#FF00FF  means:\n"
+                "   value <= -1   => not plot\n"
+                "   -1 < value <= 0 => plot with #FF0000 color\n"
+                "   0 < value  => plot with #FF00FF color\n"
+            ),
+            "plot_function": "plot_multi_color_dots_equal_on_upper_bound",
+            "value_pattern": re.compile(r'^(\+|<=)?[0-9]+\.?[0-9]?<?$'),
+            "pattern": re.compile(
+                fr'^(\+|<=)?[0-9]+\.?[0-9]?<?:(?:{color_regex}|not plot)$'
+            ),
+            "instruction": (
+                "Ex: <=-1:not plot|<=0:#FF0000|0<:#FF00FF  means:\n"
+                "   value <= -1   => not plot\n"
+                "   -1 < value <= 0 => plot with #FF0000 color\n"
+                "   0 < value  => plot with #FF00FF color\n"
+            ),
+            "default_value_color": "<=0:#FF0000|0<:#FF00FF"
+        },
+        'multiColorDotsEqualOnLowerBound': {
+            "description": (
+                "Multicolor dots in center with value/colors mapping defined "
+                "by ValueColors.\n"
+                "Ex: <-1:not plot|<0:#FF0000|=0:#FF00FF  means:\n"
+                "   value < -1   => not plot\n"
+                "   -1 =< value < 0 => plot with #FF0000 color\n"
+                "   value >= 0  => plot with #FF00FF color\n"
+            ),
+            "plot_function": "plot_multi_color_dots_equal_on_lower_bound",
+            "value_pattern": re.compile(r'^[=<]?[0-9]\.?[0-9]?$'),
+            "pattern": re.compile(
+                fr'^[=<]?[0-9]\.?[0-9]?:(?:{color_regex}|not plot)'
+            ),
+            "instruction": (
+                "Ex: <-1:not plot|<0:#FF0000|=0:#FF00FF  means:\n"
+                "   value < -1   => not plot\n"
+                "   -1 =< value < 0 => plot with #FF0000 color\n"
+                "   value >= 0  => plot with #FF00FF color\n"
+            ),
+            "default_value_color": "<0:#FF0000|=0:#00FF00"
+        },
+        'upDownDots': {
+            "description": (
+                "Show data with 2 different values: first down/ second up. "
+                "Colors defined by ValueColors.\nEx: Down:#FF0000|Up:#00FFFF"),
+            "plot_function": 'plot_up_down_dots',
+            "value_pattern": re.compile("^(0|1|Up|Down)$"),
+            "pattern": re.compile(f"^(?:Up|Down):{color_regex}$"),
+            "instruction": (
+                "Colors looks like #12ABEF\n"
+                "Ex: Down:#FF0000|Up:#00FFFF  means:\n"
+                "   value == 1 => plot above center line with #00FFFF color\n"
+                "   value == 0 => plot under center line with #FF0000 color"),
+            "default_value_color": "Down:#FF0000|Up:#00FFFF"
+        }
+    }
diff --git a/tests/database/test_extract_data.py b/tests/database/test_extract_data.py
index b814084dfa3d46d59fde46595f762ebeac7ba6cc..f92f4ca67451f6dddf5ca63a15504b5d499c7cdd 100644
--- a/tests/database/test_extract_data.py
+++ b/tests/database/test_extract_data.py
@@ -26,7 +26,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                            'dbLabel': None,
                            'label': 'SOH/Data Def',
                            'fixPoint': 0,
-                           'valueColors': '0:W|1:C'}
+                           'valueColors': 'Down:#FFFFFF|Up:#00FFFF'}
         self.assertDictEqual(get_chan_plot_info('SOH/Data Def', 'RT130'),
                              expected_result)
 
@@ -42,7 +42,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                                'dbLabel': 'MassPos',
                                'label': 'VM1-MassPos',
                                'fixPoint': 1,
-                               'valueColors': None}
+                               'valueColors': ''}
             self.assertDictEqual(get_chan_plot_info('VM1', 'Q330'),
                                  expected_result)
 
@@ -57,7 +57,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                                'dbLabel': None,
                                'label': 'MassPos1',
                                'fixPoint': 1,
-                               'valueColors': None}
+                               'valueColors': ''}
             self.assertDictEqual(get_chan_plot_info('MassPos1', 'RT130'),
                                  expected_result)
 
@@ -72,7 +72,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                                'convertFactor': 1,
                                'dbLabel': None,
                                'fixPoint': 0,
-                               'valueColors': None,
+                               'valueColors': '',
                                'label': 'DS2'}
             self.assertDictEqual(get_chan_plot_info('DS2', 'RT130'),
                                  expected_result)
@@ -87,7 +87,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                                'convertFactor': 1,
                                'dbLabel': 'SeismicData',
                                'fixPoint': 0,
-                               'valueColors': None,
+                               'valueColors': '',
                                'label': 'LHE-EW'}
             self.assertDictEqual(get_chan_plot_info('LHE', 'Q330'),
                                  expected_result)
@@ -108,7 +108,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                            'dbLabel': '',
                            'label': 'DEFAULT-Bad Channel ID',
                            'fixPoint': 0,
-                           'valueColors': None}
+                           'valueColors': ''}
         self.assertDictEqual(get_chan_plot_info('Bad Channel ID', 'Unknown'),
                              expected_result)
 
@@ -123,7 +123,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                            'dbLabel': 'PhaseError',
                            'label': 'LCE-PhaseError',
                            'fixPoint': 0,
-                           'valueColors': 'L:W|D:Y'}
+                           'valueColors': 'Line:#FFFFFF|Dot:#FFFF00'}
         self.assertDictEqual(
             get_chan_plot_info('LCE', 'Unknown'),
             expected_result)
@@ -145,7 +145,7 @@ class TestGetChanPlotInfo(BaseTestCase):
                            'dbLabel': '',
                            'label': 'DEFAULT-SOH/Data Def',
                            'fixPoint': 0,
-                           'valueColors': None}
+                           'valueColors': ''}
 
         # Data type has None value. None value comes from
         # controller.processing.detect_data_type.
diff --git a/tests/model/mseed_data/test_mseed.py b/tests/model/mseed_data/test_mseed.py
index 4690a19c1211a2dae330e9958db61669fc0d1827..b29a131b818d185c35b40419d1c425c24e93f8db 100644
--- a/tests/model/mseed_data/test_mseed.py
+++ b/tests/model/mseed_data/test_mseed.py
@@ -359,20 +359,7 @@ class TestMSeed(BaseTestCase):
         self.assertEqual(obj.soh_data['3734']['EX1']['endTmEpoch'],
                          1534550340.0)
         self.assertEqual(obj.soh_data['3734']['EX1']['size'], 597)
-        expected_gaps = [
-            [1534465500.0, 1534465800.0], [1534469100.0, 1534469400.0],
-            [1534472700.0, 1534473000.0], [1534476300.0, 1534476600.0],
-            [1534479900.0, 1534480200.0], [1534483500.0, 1534483800.0],
-            [1534487100.0, 1534487400.0], [1534490700.0, 1534491000.0],
-            [1534494300.0, 1534494600.0], [1534497900.0, 1534498200.0],
-            [1534501500.0, 1534501800.0], [1534505100.0, 1534505400.0],
-            [1534508700.0, 1534509000.0], [1534512300.0, 1534512600.0],
-            [1534515900.0, 1534516200.0], [1534519500.0, 1534519800.0],
-            [1534521360.0, 1534524000.0], [1534527300.0, 1534527600.0],
-            [1534530900.0, 1534531200.0], [1534534500.0, 1534534800.0],
-            [1534538100.0, 1534538400.0], [1534541700.0, 1534542000.0],
-            [1534545300.0, 1534545600.0], [1534548900.0, 1534549200.0]
-        ]
+        expected_gaps = [[1534521360.0, 1534524000.0]]
         self.assertEqual(obj.gaps['3734'], expected_gaps)
 
     def test_not_detect_gap(self):
diff --git a/tests/model/mseed_data/test_mseed_reader.py b/tests/model/mseed_data/test_mseed_reader.py
index 8d54b99e81a72c34d6cc541c98dbde565de14540..d522f284c3c32bf9ee45b9861dc7a65534cc0132 100644
--- a/tests/model/mseed_data/test_mseed_reader.py
+++ b/tests/model/mseed_data/test_mseed_reader.py
@@ -284,13 +284,7 @@ class TestMSeedReader(BaseTestCase):
         self.assertEqual(self.soh_data['3734']['EX1']['endTmEpoch'],
                          1534550340.0)
         self.assertEqual(self.soh_data['3734']['EX1']['size'], 597)
-        expected_gaps = [
-            [1534515900.0, 1534515960.0], [1534519020.0, 1534519080.0],
-            [1534522140.0, 1534523940.0], [1534526820.0, 1534526880.0],
-            [1534529940.0, 1534530000.0], [1534533060.0, 1534533120.0],
-            [1534536180.0, 1534536240.0], [1534539300.0, 1534539360.0],
-            [1534542420.0, 1534542480.0], [1534545540.0, 1534545600.0],
-            [1534548660.0, 1534548720.0]]
+        expected_gaps = [[1534522140.0, 1534523940.0]]
         self.assertEqual(self.soh_data['3734']['EX1']['gaps'], expected_gaps)
 
     def test_not_detect_gap(self):
diff --git a/tests/view/db_config/value_color_helper/test_functions.py b/tests/view/db_config/value_color_helper/test_functions.py
index c07285c61acbe7422d43663f65c18839810806e1..c55a0de0173bde2c102962d88c99fed47cb7d772 100644
--- a/tests/view/db_config/value_color_helper/test_functions.py
+++ b/tests/view/db_config/value_color_helper/test_functions.py
@@ -16,7 +16,7 @@ class TestConvertValueColorStr(BaseTestCase):
             result = convert_value_color_str('linesDots', 'L:G')
             self.assertEqual(result, expected_value_colors)
         with self.subTest("Old format of default value which is empty string"):
-            expected_value_colors = "Line:#00FF00"
+            expected_value_colors = ""
             result = convert_value_color_str('linesDots', '')
             self.assertEqual(result, expected_value_colors)
         with self.subTest("New format of both line and dot value"):
@@ -86,51 +86,33 @@ class TestConvertValueColorStr(BaseTestCase):
 
     def test_incorrect_format(self):
         with self.subTest("triColorLines"):
-            expected_value_colors = ('unrecognized:=1:#FF00FF'
-                                     '|unrecognized:*0:#FF0000'
-                                     '|unrecognized:1.1:#00FF00')
-            result = convert_value_color_str(
-                'triColorLines',
-                '=1:M|*0:R|1.1:G')
-            self.assertEqual(result, expected_value_colors)
+            with self.assertRaises(ValueError):
+                convert_value_color_str(
+                    'triColorLines',
+                    '=1:M|*0:R|1.1:G')
         with self.subTest("upDownDots"):
-            expected_value_colors = ('unrecognized:2:#FF00FF'
-                                     '|unrecognized:Line:#FF0000'
-                                     '|unrecognized:1.1:#00FF00'
-                                     '|unrecognized:L:#FFFF00')
-            result = convert_value_color_str(
-                'upDownDots',
-                '2:M|Line:R|1.1:G|L:Y')
-            self.assertEqual(result, expected_value_colors)
+            with self.assertRaises(ValueError):
+                convert_value_color_str(
+                    'upDownDots',
+                    '2:M|Line:R|1.1:G|L:Y')
         with self.subTest("linesDots"):
-            expected_value_colors = ('unrecognized:1:#FF00FF'
-                                     '|unrecognized:Up:#FF0000'
-                                     '|unrecognized:1.1:#00FF00'
-                                     '|Line:#FFFF00')
-            result = convert_value_color_str(
-                'linesDots',
-                '1:M|Up:R|1.1:G|L:Y')
-            self.assertEqual(result, expected_value_colors)
+            with self.assertRaises(ValueError):
+                convert_value_color_str(
+                    'linesDots',
+                    '1:M|Up:R|1.1:G|L:Y')
         with self.subTest("multiColorDotsEqualOnUpperBound"):
-            expected_value_colors = ('unrecognized:*3:#FF0000'
-                                     '|unrecognized:<3.3:#FFFF00'
-                                     '|unrecognized:=3.3:#00FF00')
-            result = convert_value_color_str(
-                'multiColorDotsEqualOnUpperBound',
-                '*3:#FF0000|<3.3:#FFFF00|=3.3:#00FF00')
-            self.assertEqual(result, expected_value_colors)
+            with self.assertRaises(ValueError):
+                convert_value_color_str(
+                    'multiColorDotsEqualOnUpperBound',
+                    '*3:#FF0000|<3.3:#FFFF00|=3.3:#00FF00')
         with self.subTest("multiColorDotsEqualOnLowerBound"):
-            expected_value_colors = ('unrecognized:+0:#FF0000'
-                                     '|unrecognized:-1:#FFFF00'
-                                     '|unrecognized:<=2:#00FF00'
-                                     '|unrecognized:2<:#FF00FF')
-            result = convert_value_color_str(
-                'multiColorDotsEqualOnLowerBound',
-                '+0:R|-1:Y|<=2:G|2<:M')
-            self.assertEqual(result, expected_value_colors)
+            with self.assertRaises(ValueError):
+                convert_value_color_str(
+                    'multiColorDotsEqualOnLowerBound',
+                    '+0:R|-1:Y|<=2:G|2<:M')
 
-    def test_old_value_color_str_is_none(self):
-        result = convert_value_color_str('linesSRate', None)
+    def test_old_value_color_str_is_empty(self):
+        result = convert_value_color_str('linesSRate', '')
         self.assertEqual(result, '')
 
 
@@ -198,3 +180,17 @@ class TestPrepareValueColorHTML(BaseTestCase):
         )
         result = prepare_value_color_html('-1:#FF00FF|0:#FF0000|1:#00FF00')
         self.assertEqual(result, expected_value_colors)
+
+    def test_dot_for_time(self):
+        expected_value_colors = (
+            "<p>C:"
+            "<span style='color: #FF00FF; font-size:25px;'>&#8718;</span>"
+            "</p>"
+        )
+        result = prepare_value_color_html('C:#FF00FF')
+        self.assertEqual(result, expected_value_colors)
+
+    def test_empty_input(self):
+        expected = ''
+        actual = prepare_value_color_html('')
+        self.assertEqual(expected, actual)
diff --git a/tests/view/plotting/plotting_widget/test_plotting_helper.py b/tests/view/plotting/plotting_widget/test_plotting_helper.py
index ddf6d12b9e1d1ce6cf77ddbc8fcdfcfe12b523c7..3e6bbfc7041ce6952aaa2fb099d26841207ecf13 100644
--- a/tests/view/plotting/plotting_widget/test_plotting_helper.py
+++ b/tests/view/plotting/plotting_widget/test_plotting_helper.py
@@ -112,29 +112,24 @@ class TestGetCategorizedDataFromValueColorEqualOnUpperBound(BaseTestCase):
             'data': [[1, 3, 3.2, 3.3, 3.4]]}
 
     def test_equal_on_upper_bound(self):
-        chan_db_info = {'valueColors': '3:R|3.3:Y|+3.3:G'}
+        chan_db_info = {
+            'valueColors': '<=3:#FF0000|<=3.3:#00FFFF|3.3<:#00FF00'
+        }
         points_list, colors = \
             get_categorized_data_from_value_color_equal_on_upper_bound(
                 self.c_data, chan_db_info)
         self.assertEqual(points_list, [[0, 1], [2, 3], [4]])
-        self.assertEqual(colors, ['R', 'Y', 'G'])
+        self.assertEqual(colors, ['#FF0000', '#00FFFF', '#00FF00'])
 
-    def test_underscore(self):
-        chan_db_info = {'valueColors': '3:_|3.3:Y|+3.3:G'}
+    def test_not_plot(self):
+        chan_db_info = {
+            'valueColors': '<=3:not plot|<=3.3:#00FFFF|3.3<:#00FF00'
+        }
         points_list, colors = \
             get_categorized_data_from_value_color_equal_on_upper_bound(
                 self.c_data, chan_db_info)
         self.assertEqual(points_list, [[2, 3], [4]])
-        self.assertEqual(colors, ['Y', 'G'])
-
-    def test_star(self):
-        chan_db_info = {'valueColors': '*:G'}
-        points_list, colors = \
-            get_categorized_data_from_value_color_equal_on_upper_bound(
-                self.c_data, chan_db_info)
-
-        self.assertEqual(points_list, self.c_data['times'])
-        self.assertEqual(colors, ['G'])
+        self.assertEqual(colors, ['#00FFFF', '#00FF00'])
 
 
 class TestGetCategorizedDataFromValueColorEqualOnLowerBound(BaseTestCase):