| 323 | |
| 324 | |
| 325 | def plot_histograms_stacked(per_parser_times: Dict[str, np.ndarray], viz_dir: Path) -> None: |
| 326 | # Build list of (parser, positive_times) |
| 327 | items = [] |
| 328 | for parser, times in per_parser_times.items(): |
| 329 | tpos = times[times > 0] |
| 330 | if tpos.size > 0: |
| 331 | items.append((parser, tpos)) |
| 332 | if not items: |
| 333 | return |
| 334 | |
| 335 | # Shared log-spaced bins across all parsers |
| 336 | all_pos = np.concatenate([tp for _, tp in items]) |
| 337 | tmin, tmax = float(np.min(all_pos)), float(np.max(all_pos)) |
| 338 | if not (tmin > 0 and np.isfinite(tmin) and np.isfinite(tmax) and tmin < tmax): |
| 339 | return |
| 340 | bins = np.logspace(np.log10(tmin), np.log10(tmax), 50) |
| 341 | |
| 342 | n = len(items) |
| 343 | fig, axes = plt.subplots(nrows=n, ncols=1, figsize=(9, max(2.5 * n, 4.0)), sharex=True) |
| 344 | if n == 1: |
| 345 | axes = [axes] # normalize to list |
| 346 | |
| 347 | for ax, (parser, tpos) in zip(axes, items): |
| 348 | ax.hist(tpos, bins=bins, color="#1f77b4", alpha=0.85, log=True) |
| 349 | ax.set_yscale("log") |
| 350 | ax.set_xscale("log") |
| 351 | ax.grid(True, alpha=0.3, which="both") |
| 352 | ax.set_ylabel("Count (log)") |
| 353 | ax.set_title(f"{parser} (n={tpos.size})", loc="left", fontsize=10) |
| 354 | |
| 355 | axes[-1].set_xlabel("Seconds per page (log)") |
| 356 | fig.suptitle("Page time histograms — stacked (common x-axis, log-log)", y=0.98) |
| 357 | fig.tight_layout(rect=[0, 0, 1, 0.97]) |
| 358 | out = viz_dir / "hist_stacked.png" |
| 359 | fig.savefig(out, dpi=150) |
| 360 | plt.close(fig) |
| 361 | |
| 362 | |
| 363 | # -------------- Main -------------- |