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]