diff --git a/src/evidently/ui/dashboards/reports.py b/src/evidently/ui/dashboards/reports.py
index 832b60f073..56a72ee7c1 100644
--- a/src/evidently/ui/dashboards/reports.py
+++ b/src/evidently/ui/dashboards/reports.py
@@ -26,6 +26,7 @@
from evidently.ui.dashboards.utils import PlotType
from evidently.ui.dashboards.utils import _get_hover_params
from evidently.ui.dashboards.utils import _get_metric_hover
+from evidently.ui.type_aliases import PointInfo
from evidently.ui.type_aliases import ProjectID
if TYPE_CHECKING:
@@ -59,25 +60,26 @@ async def build(
continue
for metric, pts in metric_pts.items():
- pts.sort(key=lambda x: x[0])
-
+ pts.sort(key=lambda x: x.timestamp)
hover = _get_metric_hover(hover_params[metric], val)
+ hover_args = {
+ "name": val.legend,
+ "legendgroup": val.legend,
+ "hovertemplate": hover,
+ "customdata": [
+ str({"metric_fingerprint": metric.get_fingerprint(), "snapshot_id": str(p.snapshot_id)})
+ for p in pts
+ ],
+ }
if self.plot_type == PlotType.HISTOGRAM:
- plot = go.Histogram(
- x=[p[1] for p in pts],
- name=val.legend,
- legendgroup=val.legend,
- hovertemplate=hover,
- )
+ plot = go.Histogram(x=[p.value for p in pts], **hover_args)
else:
cls, args = self.plot_type_cls
plot = cls(
- x=[p[0] for p in pts],
- y=[p[1] for p in pts],
- name=val.legend,
- legendgroup=val.legend,
- hovertemplate=hover,
+ x=[p.timestamp for p in pts],
+ y=[p.value for p in pts],
+ **hover_args,
**args,
)
fig.add_trace(plot)
@@ -125,18 +127,18 @@ async def build(
ct = CounterData.int(self.text or "", int(value))
return counter(title=self.title, counters=[ct], size=self.size)
- def _get_counter_value(self, points: Dict[Metric, List[Tuple[datetime.datetime, Any]]]) -> float:
+ def _get_counter_value(self, points: Dict[Metric, List[PointInfo]]) -> float:
if self.value is None:
raise ValueError("Counters with agg should have value")
if self.agg == CounterAgg.LAST:
if len(points) == 0:
return 0
return max(
- ((ts, v) for vs in points.values() for ts, v in vs),
+ ((pi.timestamp, pi.value) for vs in points.values() for pi in vs),
key=lambda x: x[0],
)[1]
if self.agg == CounterAgg.SUM:
- return sum(v or 0 for vs in points.values() for ts, v in vs)
+ return sum(pi.value or 0 for vs in points.values() for pi in vs)
raise ValueError(f"Unknown agg type {self.agg}")
diff --git a/src/evidently/ui/dashboards/utils.py b/src/evidently/ui/dashboards/utils.py
index a7713933d9..b40b115ead 100644
--- a/src/evidently/ui/dashboards/utils.py
+++ b/src/evidently/ui/dashboards/utils.py
@@ -116,7 +116,7 @@ def _flatten_params(obj: EvidentlyBaseModel) -> Dict[str, str]:
def _get_metric_hover(params: List[str], value: "PanelValue"):
params_join = "
".join(params)
- hover = f"Timestamp: %{{x}}
{value.field_path}: %{{y}}
{params_join}"
+ hover = f"Timestamp: %{{x}}
{value.field_path}: %{{y}}
{params_join}
%{{customdata}}"
return hover
diff --git a/src/evidently/ui/storage/local/base.py b/src/evidently/ui/storage/local/base.py
index 970ea31816..8bcbc9671d 100644
--- a/src/evidently/ui/storage/local/base.py
+++ b/src/evidently/ui/storage/local/base.py
@@ -38,6 +38,7 @@
from evidently.ui.type_aliases import BlobID
from evidently.ui.type_aliases import DataPointsAsType
from evidently.ui.type_aliases import OrgID
+from evidently.ui.type_aliases import PointInfo
from evidently.ui.type_aliases import PointType
from evidently.ui.type_aliases import ProjectID
from evidently.ui.type_aliases import SnapshotID
@@ -339,5 +340,7 @@ async def load_points_as_type(
for metric, metric_field_value in value.get(report).items():
if metric not in points[i]:
points[i][metric] = []
- points[i][metric].append((report.timestamp, self.parse_value(cls, metric_field_value)))
+ points[i][metric].append(
+ PointInfo(report.timestamp, report.id, self.parse_value(cls, metric_field_value))
+ )
return points
diff --git a/src/evidently/ui/type_aliases.py b/src/evidently/ui/type_aliases.py
index 1e6bc77e68..559885a5cc 100644
--- a/src/evidently/ui/type_aliases.py
+++ b/src/evidently/ui/type_aliases.py
@@ -1,9 +1,10 @@
+import dataclasses
import datetime
import uuid
from typing import Dict
+from typing import Generic
from typing import List
from typing import NamedTuple
-from typing import Tuple
from typing import TypeVar
from typing import Union
@@ -36,6 +37,16 @@ class TestInfo(NamedTuple):
TestResultPoints = Dict[datetime.datetime, Dict[Test, TestInfo]]
+
PointType = TypeVar("PointType")
-DataPointsAsType = List[Dict[Metric, List[Tuple[datetime.datetime, PointType]]]]
+
+
+@dataclasses.dataclass
+class PointInfo(Generic[PointType]):
+ timestamp: datetime.datetime
+ snapshot_id: SnapshotID
+ value: PointType
+
+
+DataPointsAsType = List[Dict[Metric, List[PointInfo[PointType]]]]
DataPoints = DataPointsAsType[float]