83 lines
2.5 KiB
Python
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")
|