"""Generalized time-series and distance-series logging/plotting."""
from __future__ import annotations
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from ._utils import detect_time_divisor, downsample_indices, get_time_array, time_label
[docs]
def plot_log(
df: pd.DataFrame,
columns: str | list[str],
stacked: bool = True,
domain: str = "time",
time_col: str = "time_s",
distance_col: str = "distance",
title: str | None = None,
height_per_subplot: int = 250,
max_display_hz: float = 100.0,
) -> go.Figure:
"""General-purpose log plotting function.
Parameters
----------
df : pd.DataFrame
The log DataFrame.
columns : list[str]
Column names to plot.
stacked : bool
``True`` -> one subplot per column (shared x-axis).
``False`` -> all traces on a single plot.
domain : str
``"time"`` -> x-axis is *time_col*.
``"distance"`` -> x-axis is *distance_col*.
time_col : str
Column name for time. If the DataFrame index is named
``time_s``, it is used automatically when *time_col* is
not found as a column.
distance_col : str
Column name for distance.
title : str or None
Figure title. Defaults to a generated summary.
height_per_subplot : int
Pixel height per subplot row (only used when *stacked=True*).
max_display_hz : float
Target display sample rate for naive downsampling. ``0`` or
``None`` disables downsampling.
Returns
-------
go.Figure
"""
print(
"""
DEPRECATION WARNING: plot_log is deprecated and will be integrated into existing
visualization functions in a future release.
Please use the Analyzer API instead. Analyzer.plot() handles single and dual-axis
plots, and Analyzer.subplots() provides stacked subplot layouts with a shared
time axis.
"""
)
if isinstance(columns, str):
columns = [columns]
# Resolve x-axis
if domain == "distance":
if distance_col not in df.columns:
raise KeyError(
f"Distance column '{distance_col}' not found. "
f"Run compute_elapsed_distance() first."
)
x_full = df[distance_col].values
x_label = f"{distance_col} (m)"
divisor = 1.0
else:
x_full = get_time_array(df, time_col)
divisor = detect_time_divisor(
np.asarray([x_full[0], x_full[-1]], dtype=np.float64)
)
unit = time_label(divisor)
x_label = f"Time ({unit})"
duration_s = (x_full[-1] - x_full[0]) / divisor
idx = downsample_indices(len(x_full), duration_s, max_display_hz)
x = x_full[idx]
if title is None:
mode = "stacked" if stacked else "unified"
title = f"Log Plot ({mode}, vs {domain}): {', '.join(columns[:4])}"
if len(columns) > 4:
title += f" +{len(columns) - 4} more"
if stacked:
n = len(columns)
fig = make_subplots(
rows=n,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02,
subplot_titles=columns,
)
for i, col in enumerate(columns, 1):
fig.add_trace(
go.Scattergl(x=x, y=df[col].values[idx], mode="lines", name=col),
row=i,
col=1,
)
fig.update_yaxes(title_text=col, row=i, col=1)
fig.update_xaxes(title_text=x_label, row=n, col=1)
fig.update_layout(
height=height_per_subplot * n,
title=title,
showlegend=False,
)
else:
fig = go.Figure()
for col in columns:
fig.add_trace(
go.Scattergl(x=x, y=df[col].values[idx], mode="lines", name=col)
)
fig.update_layout(
xaxis_title=x_label,
yaxis_title="Value",
title=title,
height=500,
)
return fig