feat(uti): Cover theme sync
[csit.git] / resources / tools / dash / app / pal / trending / layout.py
index 3776214..422488d 100644 (file)
 """Plotly Dash HTML layout override.
 """
 
 """Plotly Dash HTML layout override.
 """
 
-
-import json
 import pandas as pd
 
 from dash import dcc
 from dash import html
 from dash import callback_context, no_update
 import pandas as pd
 
 from dash import dcc
 from dash import html
 from dash import callback_context, no_update
-from dash import Input, Output, State, callback
+from dash import Input, Output, State
 from dash.exceptions import PreventUpdate
 from dash.exceptions import PreventUpdate
+import dash_bootstrap_components as dbc
 from yaml import load, FullLoader, YAMLError
 from datetime import datetime, timedelta
 
 from ..data.data import Data
 from yaml import load, FullLoader, YAMLError
 from datetime import datetime, timedelta
 
 from ..data.data import Data
-from .graphs import trending_tput
+from .graphs import graph_trending, graph_hdrh_latency, \
+    select_trending_data
 
 
 class Layout:
     """
     """
 
 
 
 class Layout:
     """
     """
 
-    STYLE_HIDEN = {"display": "none"}
-    STYLE_BLOCK = {"display": "block"}
-    STYLE_INLINE ={"display": "inline-block", "width": "50%"}
     NO_GRAPH = {"data": [], "layout": {}, "frames": []}
 
     def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
     NO_GRAPH = {"data": [], "layout": {}, "frames": []}
 
     def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
@@ -55,12 +52,12 @@ class Layout:
         data_mrr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
         data_mrr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
-        ).read_trending_mrr()
+        ).read_trending_mrr(days=5)
 
         data_ndrpdr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
 
         data_ndrpdr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
-        ).read_trending_ndrpdr()
+        ).read_trending_ndrpdr(days=14)
 
         self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
 
 
         self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
 
@@ -133,223 +130,297 @@ class Layout:
             return html.Div(
                 id="div-main",
                 children=[
             return html.Div(
                 id="div-main",
                 children=[
-                    dcc.Store(id="selected-tests"),
-                    self._add_ctrl_div(),
-                    self._add_plotting_div()
+                    dbc.Row(
+                        id="row-navbar",
+                        class_name="g-0",
+                        children=[
+                            self._add_navbar(),
+                        ]
+                    ),
+                    dcc.Loading(
+                        dbc.Offcanvas(
+                            id="offcanvas-metadata",
+                            title="Throughput And Latency",
+                            placement="end",
+                            is_open=False,
+                            children=[
+                                dbc.Row(id="metadata-tput-lat"),
+                                dbc.Row(id="metadata-hdrh-graph"),
+                            ]
+                        )
+                    ),
+                    dbc.Row(
+                        id="row-main",
+                        class_name="g-0 p-2",
+                        children=[
+                            dcc.Store(
+                                id="selected-tests"
+                            ),
+                            self._add_ctrl_col(),
+                            self._add_plotting_col(),
+                        ]
+                    )
                 ]
             )
         else:
             return html.Div(
                 ]
             )
         else:
             return html.Div(
-            id="div-main-error",
-            children="An Error Occured."
-        )
+                id="div-main-error",
+                children=[
+                    dbc.Alert(
+                        [
+                            "An Error Occured",
+                        ],
+                        color="danger",
+                    ),
+                ]
+            )
 
 
-    def _add_ctrl_div(self):
-        """Add div with controls. It is placed on the left side.
+    def _add_navbar(self):
+        """Add nav element with navigation panel. It is placed on the top.
         """
         """
-        return html.Div(
-            id="div-controls",
+        return dbc.NavbarSimple(
+            id="navbarsimple-main",
             children=[
             children=[
-                html.Div(
-                    id="div-controls-tabs",
-                    children=[
-                        self._add_ctrl_select(),
-                        self._add_ctrl_shown()
-                    ]
+                dbc.NavItem(
+                    dbc.NavLink(
+                        "Continuous Performance Trending",
+                        disabled=True,
+                        external_link=True,
+                        href="#"
+                    )
                 )
             ],
                 )
             ],
-            style={
-                "display": "inline-block",
-                "width": "18%",
-                "padding": "5px"
-            }
+            brand="Dashboard",
+            brand_href="/",
+            brand_external_link=True,
+            class_name="p-2",
+            fluid=True,
         )
 
         )
 
-    def _add_plotting_div(self):
-        """Add div with plots and tables. It is placed on the right side.
+    def _add_ctrl_col(self) -> dbc.Col:
+        """Add column with controls. It is placed on the left side.
         """
         """
-        return html.Div(
-            id="div-plotting-area",
+        return dbc.Col(
+            id="col-controls",
             children=[
             children=[
-                dcc.Loading(
-                    id="loading-graph",
-                    children=[
-                        dcc.Graph(
-                            id="graph-tput",
-                            style=self.STYLE_HIDEN
-                        ),
-                        dcc.Graph(
-                            id="graph-latency",
-                            style=self.STYLE_HIDEN
-                        )
-                    ],
-                    type="circle"
-                ),
-                html.Div(
-                    children=[
-                        html.Div(
-                            id="div-tput-metadata",
-                            children=[
-                                dcc.Markdown("""
-                                **Metadata**
-
-                                Click on data points in the graph.
-                                """),
-                                html.Pre(
-                                    id="tput-metadata"
-                                )
-                            ],
-                            style=self.STYLE_HIDEN
-                        ),
-                        html.Div(
-                            id="div-latency-metadata",
-                            children=[
-                                dcc.Markdown("""
-                                **Metadata**
-
-                                Click on data points in the graph.
-                                """),
-                                html.Pre(
-                                    id="latency-metadata"
-                                )
-                            ],
-                            style=self.STYLE_HIDEN
-                        )
-                    ]
-                )
+                self._add_ctrl_panel(),
+                self._add_ctrl_shown()
             ],
             ],
-            style={
-                "vertical-align": "top",
-                "display": "inline-block",
-                "width": "80%",
-                "padding": "5px"
-            }
         )
 
         )
 
-    def _add_ctrl_shown(self):
-        """
+    def _add_plotting_col(self) -> dbc.Col:
+        """Add column with plots and tables. It is placed on the right side.
         """
         """
-        return html.Div(
-            id="div-ctrl-shown",
+        return dbc.Col(
+            id="col-plotting-area",
             children=[
             children=[
-                html.H5("Selected tests"),
-                html.Div(
-                    id="container-selected-tests",
+                dbc.Row(  # Throughput
+                    id="row-graph-tput",
+                    class_name="g-0 p-2",
                     children=[
                     children=[
-                        dcc.Checklist(
-                            id="cl-selected",
-                            options=[],
-                            labelStyle={"display": "block"}
-                        ),
-                        html.Button(
-                            id="btn-sel-remove",
-                            children="Remove Selected",
-                            disabled=False
-                        ),
-                        html.Button(
-                            id="btn-sel-display",
-                            children="Display",
-                            disabled=False
+                        dcc.Loading(
+                            dcc.Graph(id="graph-tput")
                         )
                     ]
                 ),
                         )
                     ]
                 ),
-            ]
+                dbc.Row(  # Latency
+                    id="row-graph-lat",
+                    class_name="g-0 p-2",
+                    children=[
+                        dcc.Loading(
+                            dcc.Graph(id="graph-latency")
+                        )
+                    ]
+                ),
+                dbc.Row(  # Download
+                    id="div-download",
+                    class_name="g-0",
+                    children=[
+                        dcc.Loading(children=[
+                            dbc.Button(
+                                id="btn-download-data",
+                                children=["Download Data"]
+                            ),
+                            dcc.Download(id="download-data")
+                        ])
+                    ]
+                )
+            ],
+            width=9,
         )
 
         )
 
-    def _add_ctrl_select(self):
+    def _add_ctrl_panel(self) -> dbc.Row:
         """
         """
         """
         """
-        return html.Div(
-            id="div-ctrl-select",
+        return dbc.Row(
+            id="row-ctrl-panel",
+            class_name="g-0",
             children=[
             children=[
-                html.H5("Physical Test Bed Topology, NIC and Driver"),
-                dcc.Dropdown(
+                dbc.Label("Physical Test Bed Topology, NIC and Driver"),
+                dbc.Select(
                     id="dd-ctrl-phy",
                     id="dd-ctrl-phy",
+                    className="p-2",
                     placeholder="Select a Physical Test Bed Topology...",
                     placeholder="Select a Physical Test Bed Topology...",
-                    multi=False,
-                    clearable=False,
                     options=[
                         {"label": k, "value": k} for k in self.spec_tbs.keys()
                     ],
                     options=[
                         {"label": k, "value": k} for k in self.spec_tbs.keys()
                     ],
+                    size="sm",
                 ),
                 ),
-                html.H5("Area"),
-                dcc.Dropdown(
+                dbc.Label("Area"),
+                dbc.Select(
                     id="dd-ctrl-area",
                     id="dd-ctrl-area",
+                    className="p-2",
                     placeholder="Select an Area...",
                     disabled=True,
                     placeholder="Select an Area...",
                     disabled=True,
-                    multi=False,
-                    clearable=False,
+                    size="sm",
                 ),
                 ),
-                html.H5("Test"),
-                dcc.Dropdown(
+                dbc.Label("Test"),
+                dbc.Select(
                     id="dd-ctrl-test",
                     id="dd-ctrl-test",
+                    className="p-2",
                     placeholder="Select a Test...",
                     disabled=True,
                     placeholder="Select a Test...",
                     disabled=True,
-                    multi=False,
-                    clearable=False,
+                    size="sm",
                 ),
                 ),
-                html.Div(
-                    id="div-ctrl-core",
+                dbc.Row(
+                    id="row-ctrl-core",
+                    class_name="g-0",
                     children=[
                     children=[
-                        html.H5("Number of Cores"),
-                        dcc.Checklist(
-                            id="cl-ctrl-core-all",
-                            options=[{"label": "All", "value": "all"}, ],
-                            labelStyle={"display": "inline-block"}
-                        ),
-                        dcc.Checklist(
-                            id="cl-ctrl-core",
-                            labelStyle={"display": "inline-block"}
-                        )
-                    ],
-                    style={"display": "none"}
+                        dbc.Label("Number of Cores"),
+                        dbc.Col([
+                            dbc.Checklist(
+                                id="cl-ctrl-core-all",
+                                options=[{"label": "All", "value": "all"}, ],
+                                inline=False,
+                                switch=False
+                            ),
+                        ], width=3),
+                        dbc.Col([
+                            dbc.Checklist(
+                                id="cl-ctrl-core",
+                                inline=True,
+                                switch=False
+                            )
+                        ])
+                    ]
                 ),
                 ),
-                html.Div(
-                    id="div-ctrl-framesize",
+                dbc.Row(
+                    id="row-ctrl-framesize",
+                    class_name="g-0",
                     children=[
                     children=[
-                        html.H5("Frame Size"),
-                        dcc.Checklist(
-                            id="cl-ctrl-framesize-all",
-                            options=[{"label": "All", "value": "all"}, ],
-                            labelStyle={"display": "inline-block"}
-                        ),
-                        dcc.Checklist(
-                            id="cl-ctrl-framesize",
-                            labelStyle={"display": "inline-block"}
-                        )
-                    ],
-                    style={"display": "none"}
+                        dbc.Label("Frame Size"),
+                        dbc.Col([
+                            dbc.Checklist(
+                                id="cl-ctrl-framesize-all",
+                                options=[{"label": "All", "value": "all"}, ],
+                                inline=True,
+                                switch=False
+                            ),
+                        ], width=3),
+                        dbc.Col([
+                            dbc.Checklist(
+                                id="cl-ctrl-framesize",
+                                inline=True,
+                                switch=False
+                            )
+                        ])
+                    ]
                 ),
                 ),
-                html.Div(
-                    id="div-ctrl-testtype",
+                dbc.Row(
+                    id="row-ctrl-testtype",
+                    class_name="g-0",
                     children=[
                     children=[
-                        html.H5("Test Type"),
-                        dcc.Checklist(
-                            id="cl-ctrl-testtype-all",
-                            options=[{"label": "All", "value": "all"}, ],
-                            labelStyle={"display": "inline-block"}
-                        ),
-                        dcc.Checklist(
-                            id="cl-ctrl-testtype",
-                            labelStyle={"display": "inline-block"}
+                        dbc.Label("Test Type"),
+                        dbc.Col([
+                            dbc.Checklist(
+                                id="cl-ctrl-testtype-all",
+                                options=[{"label": "All", "value": "all"}, ],
+                                inline=True,
+                                switch=False
+                            ),
+                        ], width=3),
+                        dbc.Col([
+                            dbc.Checklist(
+                                id="cl-ctrl-testtype",
+                                inline=True,
+                                switch=False
+                            )
+                        ])
+                    ]
+                ),
+                dbc.Row(
+                    class_name="g-0",
+                    children=[
+                        dbc.Button(
+                            id="btn-ctrl-add",
+                            children="Add",
                         )
                         )
-                    ],
-                    style={"display": "none"}
+                    ]
                 ),
                 ),
-                html.Button(
-                    id="btn-ctrl-add",
-                    children="Add",
-                    disabled=True
+                dbc.Row(
+                    class_name="g-0",
+                    children=[
+                        dcc.DatePickerRange(
+                            id="dpr-period",
+                            min_date_allowed=\
+                                datetime.utcnow()-timedelta(days=180),
+                            max_date_allowed=datetime.utcnow(),
+                            initial_visible_month=datetime.utcnow(),
+                            start_date=datetime.utcnow() - timedelta(days=180),
+                            end_date=datetime.utcnow(),
+                            display_format="D MMMM YY"
+                        )
+                    ]
+                )
+            ]
+        )
+
+    def _add_ctrl_shown(self) -> dbc.Row:
+        """
+        """
+        return dbc.Row(
+            id="div-ctrl-shown",
+            class_name="g-0",
+            children=[
+                dbc.Row(
+                    class_name="g-0",
+                    children=[
+                        dbc.Label("Selected tests"),
+                        dbc.Checklist(
+                            id="cl-selected",
+                            options=[],
+                            inline=False
+                        )
+                    ]
                 ),
                 ),
-                html.Br(),
-                dcc.DatePickerRange(
-                    id="dpr-period",
-                    min_date_allowed=datetime.utcnow() - timedelta(days=180),
-                    max_date_allowed=datetime.utcnow(),
-                    initial_visible_month=datetime.utcnow(),
-                    start_date=datetime.utcnow() - timedelta(days=180),
-                    end_date=datetime.utcnow(),
-                    display_format="D MMMM YY"
+                dbc.Row(
+                    class_name="g-0",
+                    children=[
+                        dbc.ButtonGroup(
+                            [
+                                dbc.Button(
+                                    id="btn-sel-remove-all",
+                                    children="Remove All",
+                                    color="secondary",
+                                    disabled=False
+                                ),
+                                dbc.Button(
+                                    id="btn-sel-remove",
+                                    children="Remove Selected",
+                                    color="secondary",
+                                    disabled=False
+                                ),
+                                dbc.Button(
+                                    id="btn-sel-display",
+                                    children="Display",
+                                    color="secondary",
+                                    disabled=False
+                                )
+                            ],
+                            size="md",
+                            class_name="me-1",
+                        ),
+                    ]
                 )
             ]
         )
                 )
             ]
         )
@@ -406,13 +477,9 @@ class Layout:
             return options, disable
 
         @app.callback(
             return options, disable
 
         @app.callback(
-            Output("div-ctrl-core", "style"),
             Output("cl-ctrl-core", "options"),
             Output("cl-ctrl-core", "options"),
-            Output("div-ctrl-framesize", "style"),
             Output("cl-ctrl-framesize", "options"),
             Output("cl-ctrl-framesize", "options"),
-            Output("div-ctrl-testtype", "style"),
             Output("cl-ctrl-testtype", "options"),
             Output("cl-ctrl-testtype", "options"),
-            Output("btn-ctrl-add", "disabled"),
             State("dd-ctrl-phy", "value"),
             State("dd-ctrl-area", "value"),
             Input("dd-ctrl-test", "value"),
             State("dd-ctrl-phy", "value"),
             State("dd-ctrl-area", "value"),
             Input("dd-ctrl-test", "value"),
@@ -424,36 +491,27 @@ class Layout:
             if test is None:
                 raise PreventUpdate
 
             if test is None:
                 raise PreventUpdate
 
-            core_style = {"display": "none"}
             core_opts = []
             core_opts = []
-            framesize_style = {"display": "none"}
             framesize_opts = []
             framesize_opts = []
-            testtype_style = {"display": "none"}
             testtype_opts = []
             testtype_opts = []
-            add_disabled = True
             if phy and area and test:
             if phy and area and test:
-                core_style = {"display": "block"}
                 core_opts = [
                     {"label": v, "value": v}
                         for v in self.spec_tbs[phy][area]["core"]
                 ]
                 core_opts = [
                     {"label": v, "value": v}
                         for v in self.spec_tbs[phy][area]["core"]
                 ]
-                framesize_style = {"display": "block"}
                 framesize_opts = [
                     {"label": v, "value": v}
                         for v in self.spec_tbs[phy][area]["frame-size"]
                 ]
                 framesize_opts = [
                     {"label": v, "value": v}
                         for v in self.spec_tbs[phy][area]["frame-size"]
                 ]
-                testtype_style = {"display": "block"}
                 testtype_opts = [
                     {"label": v, "value": v}
                         for v in self.spec_tbs[phy][area]["test-type"]
                 ]
                 testtype_opts = [
                     {"label": v, "value": v}
                         for v in self.spec_tbs[phy][area]["test-type"]
                 ]
-                add_disabled = False
 
             return (
 
             return (
-                core_style, core_opts,
-                framesize_style, framesize_opts,
-                testtype_style, testtype_opts,
-                add_disabled
+                core_opts,
+                framesize_opts,
+                testtype_opts,
             )
 
         def _sync_checklists(opt, sel, all, id):
             )
 
         def _sync_checklists(opt, sel, all, id):
@@ -502,11 +560,7 @@ class Layout:
 
         @app.callback(
             Output("graph-tput", "figure"),
 
         @app.callback(
             Output("graph-tput", "figure"),
-            Output("graph-tput", "style"),
-            Output("div-tput-metadata", "style"),
             Output("graph-latency", "figure"),
             Output("graph-latency", "figure"),
-            Output("graph-latency", "style"),
-            Output("div-latency-metadata", "style"),
             Output("selected-tests", "data"),  # Store
             Output("cl-selected", "options"),  # User selection
             Output("dd-ctrl-phy", "value"),
             Output("selected-tests", "data"),  # Store
             Output("cl-selected", "options"),  # User selection
             Output("dd-ctrl-phy", "value"),
@@ -523,17 +577,19 @@ class Layout:
             Input("btn-ctrl-add", "n_clicks"),
             Input("btn-sel-display", "n_clicks"),
             Input("btn-sel-remove", "n_clicks"),
             Input("btn-ctrl-add", "n_clicks"),
             Input("btn-sel-display", "n_clicks"),
             Input("btn-sel-remove", "n_clicks"),
+            Input("btn-sel-remove-all", "n_clicks"),
             Input("dpr-period", "start_date"),
             Input("dpr-period", "end_date"),
             prevent_initial_call=True
         )
         def _process_list(store_sel, list_sel, phy, area, test, cores,
                 framesizes, testtypes, btn_add, btn_display, btn_remove,
             Input("dpr-period", "start_date"),
             Input("dpr-period", "end_date"),
             prevent_initial_call=True
         )
         def _process_list(store_sel, list_sel, phy, area, test, cores,
                 framesizes, testtypes, btn_add, btn_display, btn_remove,
-                d_start, d_end):
+                btn_remove_all, d_start, d_end):
             """
             """
 
             """
             """
 
-            if not (btn_add or btn_display or btn_remove or d_start or d_end):
+            if not (btn_add or btn_display or btn_remove or btn_remove_all or \
+                    d_start or d_end):
                 raise PreventUpdate
 
             def _list_tests():
                 raise PreventUpdate
 
             def _list_tests():
@@ -549,11 +605,7 @@ class Layout:
                 def __init__(self) -> None:
                     self._output = {
                         "graph-tput-figure": no_update,
                 def __init__(self) -> None:
                     self._output = {
                         "graph-tput-figure": no_update,
-                        "graph-tput-style": no_update,
-                        "div-tput-metadata-style": no_update,
                         "graph-lat-figure": no_update,
                         "graph-lat-figure": no_update,
-                        "graph-lat-style": no_update,
-                        "div-lat-metadata-style": no_update,
                         "selected-tests-data": no_update,
                         "cl-selected-options": no_update,
                         "dd-ctrl-phy-value": no_update,
                         "selected-tests-data": no_update,
                         "cl-selected-options": no_update,
                         "dd-ctrl-phy-value": no_update,
@@ -615,24 +667,22 @@ class Layout:
                 })
 
             elif trigger_id in ("btn-sel-display", "dpr-period"):
                 })
 
             elif trigger_id in ("btn-sel-display", "dpr-period"):
-                fig_tput, fig_lat = trending_tput(
+                fig_tput, fig_lat = graph_trending(
                     self.data, store_sel, self.layout, d_start, d_end
                 )
                 output.set_values({
                     "graph-tput-figure": \
                         fig_tput if fig_tput else self.NO_GRAPH,
                     self.data, store_sel, self.layout, d_start, d_end
                 )
                 output.set_values({
                     "graph-tput-figure": \
                         fig_tput if fig_tput else self.NO_GRAPH,
-                    "graph-tput-style": \
-                        self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
-                    "div-tput-metadata-style": \
-                        self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
                     "graph-lat-figure": \
                         fig_lat if fig_lat else self.NO_GRAPH,
                     "graph-lat-figure": \
                         fig_lat if fig_lat else self.NO_GRAPH,
-                    "graph-lat-style": \
-                        self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
-                    "div-lat-metadata-style": \
-                        self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN
                 })
                 })
-
+            elif trigger_id == "btn-sel-remove-all":
+                output.set_values({
+                    "graph-tput-figure": self.NO_GRAPH,
+                    "graph-lat-figure": self.NO_GRAPH,
+                    "selected-tests-data": list(),
+                    "cl-selected-options": list()
+                })
             elif trigger_id == "btn-sel-remove":
                 if list_sel:
                     new_store_sel = list()
             elif trigger_id == "btn-sel-remove":
                 if list_sel:
                     new_store_sel = list()
@@ -641,33 +691,21 @@ class Layout:
                             new_store_sel.append(item)
                     store_sel = new_store_sel
                 if store_sel:
                             new_store_sel.append(item)
                     store_sel = new_store_sel
                 if store_sel:
-                    fig_tput, fig_lat = trending_tput(
+                    fig_tput, fig_lat = graph_trending(
                         self.data, store_sel, self.layout, d_start, d_end
                     )
                     output.set_values({
                         "graph-tput-figure": \
                             fig_tput if fig_tput else self.NO_GRAPH,
                         self.data, store_sel, self.layout, d_start, d_end
                     )
                     output.set_values({
                         "graph-tput-figure": \
                             fig_tput if fig_tput else self.NO_GRAPH,
-                        "graph-tput-style": \
-                            self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
-                        "div-tput-metadata-style": \
-                            self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
                         "graph-lat-figure": \
                             fig_lat if fig_lat else self.NO_GRAPH,
                         "graph-lat-figure": \
                             fig_lat if fig_lat else self.NO_GRAPH,
-                        "graph-lat-style": \
-                            self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
-                        "div-lat-metadata-style": \
-                            self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN,
                         "selected-tests-data": store_sel,
                         "cl-selected-options": _list_tests()
                     })
                 else:
                     output.set_values({
                         "graph-tput-figure": self.NO_GRAPH,
                         "selected-tests-data": store_sel,
                         "cl-selected-options": _list_tests()
                     })
                 else:
                     output.set_values({
                         "graph-tput-figure": self.NO_GRAPH,
-                        "graph-tput-style": self.STYLE_HIDEN,
-                        "div-tput-metadata-style": self.STYLE_HIDEN,
                         "graph-lat-figure": self.NO_GRAPH,
                         "graph-lat-figure": self.NO_GRAPH,
-                        "graph-lat-style": self.STYLE_HIDEN,
-                        "div-lat-metadata-style": self.STYLE_HIDEN,
                         "selected-tests-data": store_sel,
                         "cl-selected-options": _list_tests()
                     })
                         "selected-tests-data": store_sel,
                         "cl-selected-options": _list_tests()
                     })
@@ -675,19 +713,74 @@ class Layout:
             return output.value()
 
         @app.callback(
             return output.value()
 
         @app.callback(
-            Output("tput-metadata", "children"),
-            Input("graph-tput", "clickData")
+            Output("metadata-tput-lat", "children"),
+            Output("metadata-hdrh-graph", "children"),
+            Output("offcanvas-metadata", "is_open"),
+            Input("graph-tput", "clickData"),
+            Input("graph-latency", "clickData")
         )
         )
-        def _show_tput_metadata(hover_data):
-            if not hover_data:
+        def _show_tput_metadata(tput_data, lat_data) -> dbc.Card:
+            """
+            """
+            if not (tput_data or lat_data):
                 raise PreventUpdate
                 raise PreventUpdate
-            return json.dumps(hover_data, indent=2)
+
+            metadata = no_update
+            graph = list()
+
+            trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
+            if trigger_id == "graph-tput":
+                title = "Throughput"
+                txt = tput_data["points"][0]["text"].replace("<br>", "\n")
+            elif trigger_id == "graph-latency":
+                title = "Latency"
+                txt = lat_data["points"][0]["text"].replace("<br>", "\n")
+                hdrh_data = lat_data["points"][0].get("customdata", None)
+                if hdrh_data:
+                    graph = [dcc.Graph(
+                        id="hdrh-latency-graph",
+                        figure=graph_hdrh_latency(hdrh_data, self.layout)
+                    ), ]
+
+            metadata = [
+                dbc.Card(
+                    children=[
+                        dbc.CardHeader(children=[
+                            dcc.Clipboard(
+                                target_id="tput-lat-metadata",
+                                title="Copy",
+                                style={"display": "inline-block"}
+                            ),
+                            title
+                        ]),
+                        dbc.CardBody(
+                            id="tput-lat-metadata",
+                            children=[txt]
+                        )
+                    ]
+                )
+            ]
+
+            return metadata, graph, True
 
         @app.callback(
 
         @app.callback(
-            Output("latency-metadata", "children"),
-            Input("graph-latency", "clickData")
+            Output("download-data", "data"),
+            State("selected-tests", "data"),
+            Input("btn-download-data", "n_clicks"),
+            prevent_initial_call=True
         )
         )
-        def _show_latency_metadata(hover_data):
-            if not hover_data:
+        def _download_data(store_sel, n_clicks):
+            """
+            """
+
+            if not n_clicks:
                 raise PreventUpdate
                 raise PreventUpdate
-            return json.dumps(hover_data, indent=2)
+
+            df = pd.DataFrame()
+            for itm in store_sel:
+                sel_data = select_trending_data(self.data, itm)
+                if sel_data is None:
+                    continue
+                df = pd.concat([df, sel_data], ignore_index=True)
+
+            return dcc.send_data_frame(df.to_csv, "trending_data.csv")