from __future__ import annotations import altair as alt import pandas as pd def build_trend_chart(dataframe: pd.DataFrame, show_error_bands: bool = False) -> alt.Chart | None: if dataframe.empty: return None chart_data = dataframe.copy() chart_data = chart_data.dropna(subset=["timestamp", "value"]) if chart_data.empty: return None chart_data["repo_class"] = chart_data["repo_id"] + " | " + chart_data["class"] base = alt.Chart(chart_data).encode( x=alt.X("timestamp:T", title="Time"), y=alt.Y("value:Q", title="Metric value"), color=alt.Color("repo_id:N", title="Repo"), strokeDash=alt.StrokeDash("class:N", title="Class"), tooltip=[ alt.Tooltip("timestamp:T", title="Timestamp"), alt.Tooltip("repo_id:N", title="Repo"), alt.Tooltip("revision:N", title="Revision"), alt.Tooltip("class:N", title="Class"), alt.Tooltip("metric:N", title="Metric"), alt.Tooltip("value:Q", title="Value", format=".4f"), alt.Tooltip("std:Q", title="Std", format=".4f"), ], ) line = base.mark_line(point=True) chart: alt.Chart | alt.LayerChart = line band_data = chart_data.dropna(subset=["std"]).copy() if show_error_bands and not band_data.empty: band_data["lower"] = band_data["value"] - band_data["std"] band_data["upper"] = band_data["value"] + band_data["std"] band = alt.Chart(band_data).mark_area(opacity=0.15).encode( x=alt.X("timestamp:T", title="Time"), y=alt.Y("lower:Q", title="Metric value"), y2="upper:Q", color=alt.Color("repo_id:N", title="Repo"), detail="repo_class:N", ) chart = band + line metric_count = chart_data["metric"].nunique() if metric_count > 1: return chart.facet(row=alt.Row("metric:N", title=None)).resolve_scale(y="independent") return chart def build_latest_comparison_chart(dataframe: pd.DataFrame, metric: str) -> alt.Chart | None: if dataframe.empty: return None latest = dataframe[dataframe["is_latest"] & (dataframe["metric"] == metric)].dropna(subset=["value"]) if latest.empty: return None return ( alt.Chart(latest) .mark_bar() .encode( x=alt.X("repo_id:N", title="Repo", sort="-y"), y=alt.Y("value:Q", title=f"Latest {metric}"), color=alt.Color("class:N", title="Class"), tooltip=[ alt.Tooltip("repo_id:N", title="Repo"), alt.Tooltip("revision:N", title="Revision"), alt.Tooltip("class:N", title="Class"), alt.Tooltip("value:Q", title="Value", format=".4f"), alt.Tooltip("std:Q", title="Std", format=".4f"), ], ) ) def build_class_small_multiples(dataframe: pd.DataFrame, metric: str) -> alt.Chart | None: if dataframe.empty: return None plot_df = dataframe[dataframe["metric"] == metric].dropna(subset=["value"]) if plot_df.empty: return None chart = ( alt.Chart(plot_df) .mark_line(point=True) .encode( x=alt.X("timestamp:T", title="Time"), y=alt.Y("value:Q", title=metric), color=alt.Color("repo_id:N", title="Repo"), tooltip=[ alt.Tooltip("timestamp:T", title="Timestamp"), alt.Tooltip("repo_id:N", title="Repo"), alt.Tooltip("revision:N", title="Revision"), alt.Tooltip("class:N", title="Class"), alt.Tooltip("value:Q", title="Value", format=".4f"), ], ) ) return chart.facet(column=alt.Column("class:N", title=None, sort="ascending"))