|
36 | 36 |
|
37 | 37 | import matplotlib.pyplot as plt |
38 | 38 | import numpy as np |
| 39 | +import pandas as pd |
| 40 | +import seaborn as sns |
39 | 41 | from matplotlib.ticker import MaxNLocator |
40 | 42 | import re |
41 | 43 |
|
@@ -287,45 +289,97 @@ def generate_report( |
287 | 289 | return "\n".join(report_lines), all_stats |
288 | 290 |
|
289 | 291 |
|
290 | | -def create_and_save_plots( |
| 292 | +def create_and_save_plots_seaborn_optimized( |
291 | 293 | all_metrics_data: Dict[str, List[Any]], |
292 | 294 | output_dir: Path, |
293 | 295 | ) -> None: |
294 | 296 | """ |
295 | | - Creates and saves line charts for each metric. |
| 297 | + Creates and saves enhanced, readable line charts for each metric using seaborn. |
| 298 | +
|
| 299 | + For large datasets, markers are decimated to avoid clutter. Each plot includes |
| 300 | + the original data line and a smoothed trendline. |
296 | 301 |
|
297 | 302 | Args: |
298 | 303 | all_metrics_data (Dict[str, List[Any]]): The prepared metrics data. |
299 | 304 | output_dir (Path): The directory where plots will be saved. |
300 | 305 | """ |
301 | 306 | print(f"Generating and saving plots to {output_dir}...") |
302 | | - sample_indices = range(len(all_metrics_data["timestamps"])) |
| 307 | + |
| 308 | + # Set the aesthetic style of the plots |
| 309 | + sns.set_theme(style="whitegrid", palette="muted") |
303 | 310 |
|
304 | 311 | for friendly_name, key in METRICS_TO_ANALYZE: |
305 | | - metric_data = all_metrics_data.get(key, []) |
306 | | - if not metric_data: |
307 | | - print(f"Skipping plot for '{friendly_name}' due to no data.") |
| 312 | + metric_data = all_metrics_data.get(key) |
| 313 | + |
| 314 | + if not metric_data or len(metric_data) < 2: |
| 315 | + num_points = len(metric_data) if metric_data else 0 |
| 316 | + print(f"Skipping plot for '{friendly_name}' due to insufficient data (found {num_points} points).") |
308 | 317 | continue |
309 | 318 |
|
310 | | - fig, ax = plt.subplots(figsize=(12, 6)) |
311 | | - ax.plot(sample_indices, metric_data, marker=".", linestyle="-", markersize=4) |
| 319 | + num_points = len(metric_data) |
| 320 | + print(f" - Processing '{friendly_name}' with {num_points} data points...") |
| 321 | + |
| 322 | + # Create a pandas DataFrame |
| 323 | + df = pd.DataFrame({ |
| 324 | + "Sample Index": range(num_points), |
| 325 | + friendly_name: metric_data |
| 326 | + }) |
| 327 | + |
| 328 | + # Create the plot |
| 329 | + fig, ax = plt.subplots(figsize=(14, 7)) |
| 330 | + |
| 331 | + # 1. Plot the original data with no markers |
| 332 | + sns.lineplot( |
| 333 | + data=df, |
| 334 | + x="Sample Index", |
| 335 | + y=friendly_name, |
| 336 | + ax=ax, |
| 337 | + label="Original Data", |
| 338 | + color=sns.color_palette("muted")[0] |
| 339 | + ) |
| 340 | + |
| 341 | + # 2. Add a smoothed trendline using LOWESS regression |
| 342 | + try: |
| 343 | + sns.regplot( |
| 344 | + data=df, |
| 345 | + x="Sample Index", |
| 346 | + y=friendly_name, |
| 347 | + ax=ax, |
| 348 | + lowess=True, |
| 349 | + scatter=False, |
| 350 | + label="Smoothed Trend", |
| 351 | + color=sns.color_palette("muted")[1], |
| 352 | + line_kws={'linewidth': 2.5}, |
| 353 | + ci=None # Disable confidence interval for a cleaner look |
| 354 | + ) |
| 355 | + except Exception as e: |
| 356 | + print(f" - Could not generate smoothed trend for '{friendly_name}': {e}") |
312 | 357 |
|
313 | | - ax.set_title(f"{friendly_name} Over Time", fontsize=16) |
314 | | - ax.set_xlabel("Sample Index", fontsize=12) |
315 | | - ax.set_ylabel(friendly_name, fontsize=12) |
316 | | - ax.grid(True, which="both", linestyle="--", linewidth=0.5) |
| 358 | + # --- Customize the plot --- |
| 359 | + ax.set_title(f"{friendly_name} Over Time", fontsize=18, fontweight='bold', pad=20) |
| 360 | + ax.set_xlabel("Sample Index", fontsize=14) |
| 361 | + ax.set_ylabel(friendly_name, fontsize=14) |
317 | 362 |
|
318 | | - # Ensure x-axis has integer ticks |
319 | 363 | ax.xaxis.set_major_locator(MaxNLocator(integer=True)) |
| 364 | + plt.xticks(rotation=0) |
| 365 | + |
| 366 | + ax.legend(title="Legend", frameon=True, fontsize=12) |
| 367 | + ax.grid(True, which="both", linestyle="--", linewidth=0.5) |
320 | 368 |
|
321 | 369 | fig.tight_layout() |
322 | 370 |
|
323 | | - # Sanitize filename |
| 371 | + # --- Sanitize filename and save the plot --- |
324 | 372 | filename_key = key.replace(" ", "_").replace("(", "").replace(")", "").replace("%", "percent") |
325 | | - plot_path = output_dir / f"{filename_key}_chart.png" |
326 | | - fig.savefig(plot_path, dpi=150) |
327 | | - plt.close(fig) |
328 | | - print(f" - Saved {plot_path.name}") |
| 373 | + plot_path = output_dir / f"{filename_key}_chart_seaborn.png" |
| 374 | + |
| 375 | + try: |
| 376 | + fig.savefig(plot_path, dpi=150, bbox_inches='tight') |
| 377 | + print(f" - Saved {plot_path.name}") |
| 378 | + except Exception as e: |
| 379 | + print(f" - Failed to save plot for '{friendly_name}': {e}") |
| 380 | + finally: |
| 381 | + plt.close(fig) |
| 382 | + |
329 | 383 |
|
330 | 384 |
|
331 | 385 | def main() -> None: |
@@ -357,7 +411,7 @@ def main() -> None: |
357 | 411 |
|
358 | 412 | # 3. Optionally create and save plots |
359 | 413 | if args.plot: |
360 | | - create_and_save_plots(metrics_data, args.output_dir) |
| 414 | + create_and_save_plots_seaborn_optimized(metrics_data, args.output_dir) |
361 | 415 |
|
362 | 416 | print("Analysis complete.") |
363 | 417 |
|
|
0 commit comments