Files
ShooterHub/analyzer/charts.py
2026-03-16 16:09:19 +01:00

83 lines
2.5 KiB
Python

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