import io import base64 import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib.dates as mdates import pandas as pd def render_group_charts(groups: list, y_min: float, y_max: float) -> list: padding_fraction = 0.05 y_range = y_max - y_min if y_range == 0: y_pad = 1.0 else: y_pad = y_range * padding_fraction charts = [] for i, g in enumerate(groups): fig, ax = plt.subplots(figsize=(9, 4)) x = g["time"] y = g["speed"] ax.plot(x, y, marker="o", linewidth=1.5, markersize=5, color="#1f77b4") ax.set_ylim(y_min - y_pad, y_max + y_pad) ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S")) fig.autofmt_xdate(rotation=30) ax.set_title(f"Group {i + 1} — {len(g)} shot(s)") ax.set_xlabel("Time of Day") ax.set_ylabel("Speed") ax.grid(True, alpha=0.3) fig.tight_layout() buf = io.BytesIO() fig.savefig(buf, format="png", dpi=100) plt.close(fig) buf.seek(0) charts.append(base64.b64encode(buf.read()).decode("utf-8")) return charts def render_overview_chart(group_stats: list) -> str: """Dual-axis line chart: avg speed and avg std dev per group.""" indices = [s["group_index"] for s in group_stats] speeds = [s["mean_speed"] for s in group_stats] stds = [s["std_speed"] if s["std_speed"] is not None else 0.0 for s in group_stats] fig, ax1 = plt.subplots(figsize=(7, 3)) color_speed = "#1f77b4" color_std = "#d62728" ax1.plot(indices, speeds, marker="o", linewidth=1.8, markersize=5, color=color_speed, label="Avg speed") ax1.set_xlabel("Group") ax1.set_ylabel("Avg speed", color=color_speed) ax1.tick_params(axis="y", labelcolor=color_speed) ax1.set_xticks(indices) ax2 = ax1.twinx() ax2.plot(indices, stds, marker="s", linewidth=1.8, markersize=5, color=color_std, linestyle="--", label="Avg std dev") ax2.set_ylabel("Avg std dev", color=color_std) ax2.tick_params(axis="y", labelcolor=color_std) lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() ax1.legend(lines1 + lines2, labels1 + labels2, fontsize=8, loc="upper right") ax1.grid(True, alpha=0.3) fig.tight_layout() buf = io.BytesIO() fig.savefig(buf, format="png", dpi=100) plt.close(fig) buf.seek(0) return base64.b64encode(buf.read()).decode("utf-8")