Source code for suboptimumg.loganalysis.plotting

"""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