Update BokehFigure to allow multiple hover data lines.

This CL enables annotating lines in BokehFigure with more than one line
of hover data. The features is also used in WifiRvr tests to annotate
lines with rssi data in addition to link layer stats.

Test: Done
Bug: None

Signed-off-by: Omar El Ayach <oelayach@google.com>
Change-Id: I73e7ffb206a77aa70a1a05131dfb31e67beb8c51
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
index 7b0f1a5..68a287a 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
@@ -60,11 +60,6 @@
     ]
 
     TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
-    TOOLTIPS = [
-        ('index', '$index'),
-        ('(x,y)', '($x, $y)'),
-        ('info', '@hover_text'),
-    ]
 
     def __init__(self,
                  title=None,
@@ -100,7 +95,17 @@
             title=self.fig_property['title'],
             tools=self.TOOLS,
             output_backend='webgl')
-        self.plot.hover.tooltips = self.TOOLTIPS
+        tooltips = [
+            ('index', '$index'),
+            ('(x,y)', '($x, $y)'),
+        ]
+        hover_set = []
+        for line in self.figure_data:
+            hover_set.extend(line['hover_text'].keys())
+        hover_set = set(hover_set)
+        for item in hover_set:
+            tooltips.append((item, '@{}'.format(item)))
+        self.plot.hover.tooltips = tooltips
         self.plot.add_tools(
             bokeh.models.tools.WheelZoomTool(dimensions='width'))
         self.plot.add_tools(
@@ -110,12 +115,14 @@
         """Function to remove NaN points from bokeh plots."""
         x_data_filtered = []
         y_data_filtered = []
-        hover_text_filtered = []
-        for x, y, hover in itertools.zip_longest(x_data, y_data, hover_text):
-            if not math.isnan(y):
-                x_data_filtered.append(x)
-                y_data_filtered.append(y)
-                hover_text_filtered.append(hover)
+        hover_text_filtered = {}
+        for idx, xy in enumerate(itertools.zip_longest(x_data, y_data)):
+            if not math.isnan(xy[1]):
+                x_data_filtered.append(xy[0])
+                y_data_filtered.append(xy[1])
+                for key, value in hover_text.items():
+                    hover_text_filtered.setdefault(key, [])
+                    hover_text_filtered[key].append(value[idx])
         return x_data_filtered, y_data_filtered, hover_text_filtered
 
     def add_line(self,
@@ -152,7 +159,9 @@
         if style == 'dashed':
             style = [5, 5]
         if not hover_text:
-            hover_text = ['y={}'.format(y) for y in y_data]
+            hover_text = {'info': ['y={}'.format(y) for y in y_data]}
+        if isinstance(hover_text, list):
+            hover_text = {'info': hover_text}
         x_data_filter, y_data_filter, hover_text_filter = self._filter_line(
             x_data, y_data, hover_text)
         self.figure_data.append({
@@ -224,10 +233,10 @@
         self.init_plot()
         two_axes = False
         for line in self.figure_data:
-            source = bokeh.models.ColumnDataSource(
-                data=dict(x=line['x_data'],
-                          y=line['y_data'],
-                          hover_text=line['hover_text']))
+            data_dict = {'x': line['x_data'], 'y': line['y_data']}
+            for key, value in line['hover_text'].items():
+                data_dict[key] = value
+            source = bokeh.models.ColumnDataSource(data=data_dict)
             if line['width'] > 0:
                 self.plot.line(x='x',
                                y='y',
diff --git a/acts_tests/tests/google/wifi/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
index 0096a92..3cd1cb8 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -285,14 +285,23 @@
             self.log.warning('ValueError: Golden file not found')
 
         # Generate graph annotatios
-        rvr_result['hover_text'] = [
-            'TX MCS = {0} ({1:.1f}%). RX MCS = {2} ({3:.1f}%)'.format(
-                curr_llstats['summary']['common_tx_mcs'],
-                curr_llstats['summary']['common_tx_mcs_freq'] * 100,
-                curr_llstats['summary']['common_rx_mcs'],
-                curr_llstats['summary']['common_rx_mcs_freq'] * 100)
-            for curr_llstats in rvr_result['llstats']
-        ]
+        rvr_result['hover_text'] = {
+            'llstats': [
+                'TX MCS = {0} ({1:.1f}%). RX MCS = {2} ({3:.1f}%)'.format(
+                    curr_llstats['summary']['common_tx_mcs'],
+                    curr_llstats['summary']['common_tx_mcs_freq'] * 100,
+                    curr_llstats['summary']['common_rx_mcs'],
+                    curr_llstats['summary']['common_rx_mcs_freq'] * 100)
+                for curr_llstats in rvr_result['llstats']
+            ],
+            'rssi': [
+                '{0:.2f} [{1:.2f},{2:.2f}]'.format(
+                    rssi['signal_poll_rssi'],
+                    rssi['chain_0_rssi'],
+                    rssi['chain_1_rssi'],
+                ) for rssi in rvr_result['rssi']
+            ]
+        }
         figure.add_line(rvr_result['total_attenuation'],
                         rvr_result['throughput_receive'],
                         'Test Results',