Spaces:
Sleeping
Sleeping
| import matplotlib.pyplot as plt | |
| import pandas as pd | |
| from data import extract_model_data | |
| # Layout parameters | |
| COLUMNS = 3 | |
| # Derived constants | |
| COLUMN_WIDTH = 100 / COLUMNS # Each column takes 25% of width | |
| BAR_WIDTH = COLUMN_WIDTH * 0.8 # 80% of column width for bars | |
| BAR_MARGIN = COLUMN_WIDTH * 0.1 # 10% margin on each side | |
| # Figure dimensions | |
| FIGURE_WIDTH = 22 # Wider to accommodate columns and legend | |
| MAX_HEIGHT = 14 # Maximum height in inches | |
| MIN_HEIGHT_PER_ROW = 2.8 | |
| FIGURE_PADDING = 1 | |
| # Bar styling | |
| BAR_HEIGHT_RATIO = 0.22 # Bar height as ratio of vertical spacing | |
| VERTICAL_SPACING_RATIO = 0.2 # Base vertical position ratio | |
| AMD_BAR_OFFSET = 0.25 # AMD bar offset ratio | |
| NVIDIA_BAR_OFFSET = 0.54 # NVIDIA bar offset ratio | |
| # Colors | |
| COLORS = { | |
| 'passed': '#4CAF50', | |
| 'failed': '#E53E3E', | |
| 'skipped': '#FFD54F', | |
| 'error': '#8B0000', | |
| 'empty': "#5B5B5B" | |
| } | |
| # Font styling | |
| MODEL_NAME_FONT_SIZE = 16 | |
| LABEL_FONT_SIZE = 14 | |
| LABEL_OFFSET = 1 # Distance of label from bar | |
| FAILURE_RATE_FONT_SIZE = 28 | |
| def get_overall_stats(df: pd.DataFrame, available_models: list[str]) -> tuple[list[int], list[int]]: | |
| """Calculate overall failure rates for AMD and NVIDIA across all models.""" | |
| if df.empty or not available_models: | |
| return 0.0, 0.0 | |
| total_amd_passed = 0 | |
| total_amd_failed = 0 | |
| total_amd_skipped = 0 | |
| total_nvidia_passed = 0 | |
| total_nvidia_failed = 0 | |
| total_nvidia_skipped = 0 | |
| for model_name in available_models: | |
| if model_name not in df.index: | |
| continue | |
| row = df.loc[model_name] | |
| amd_stats, nvidia_stats = extract_model_data(row)[:2] | |
| # AMD totals | |
| total_amd_passed += amd_stats['passed'] | |
| total_amd_failed += amd_stats['failed'] + amd_stats['error'] | |
| total_amd_skipped += amd_stats['skipped'] | |
| # NVIDIA totals | |
| total_nvidia_passed += nvidia_stats['passed'] | |
| total_nvidia_failed += nvidia_stats['failed'] + nvidia_stats['error'] | |
| total_nvidia_skipped += nvidia_stats['skipped'] | |
| return [total_amd_passed, total_amd_failed, total_amd_skipped], [total_nvidia_passed, total_nvidia_failed, total_nvidia_skipped] | |
| def draw_text_and_bar( | |
| label: str, | |
| stats: dict[str, int], | |
| y_bar: float, | |
| column_left_position: float, | |
| bar_height: float, | |
| ax: plt.Axes, | |
| ) -> None: | |
| """Draw a horizontal bar chart for given stats and its label on the left.""" | |
| # Text | |
| label_x = column_left_position - LABEL_OFFSET | |
| failures_present = any(stats[category] > 0 for category in ['failed', 'error']) | |
| if failures_present: | |
| props = dict(boxstyle='round', facecolor=COLORS['failed'], alpha=0.35) | |
| else: | |
| props = dict(alpha=0) | |
| ax.text( | |
| label_x, y_bar, label, ha='right', va='center', color='#CCCCCC', fontsize=LABEL_FONT_SIZE, | |
| fontfamily='monospace', fontweight='normal', bbox=props | |
| ) | |
| # Bar | |
| total = sum(stats.values()) | |
| if total > 0: | |
| left = column_left_position | |
| for category in ['passed', 'failed', 'skipped', 'error']: | |
| if stats[category] > 0: | |
| width = stats[category] / total * BAR_WIDTH | |
| ax.barh(y_bar, width, left=left, height=bar_height, color=COLORS[category], alpha=0.9) | |
| left += width | |
| else: | |
| ax.barh(y_bar, BAR_WIDTH, left=column_left_position, height=bar_height, color=COLORS['empty'], alpha=0.9) | |
| def create_summary_page(df: pd.DataFrame, available_models: list[str]) -> plt.Figure: | |
| """Create a summary page with model names and both AMD/NVIDIA test stats bars.""" | |
| if df.empty: | |
| fig, ax = plt.subplots(figsize=(16, 8), facecolor='#000000') | |
| ax.set_facecolor('#000000') | |
| ax.text(0.5, 0.5, 'No data available', | |
| horizontalalignment='center', verticalalignment='center', | |
| transform=ax.transAxes, fontsize=20, color='#888888', | |
| fontfamily='monospace', weight='normal') | |
| ax.axis('off') | |
| return fig | |
| # Calculate overall failure rates | |
| amd_counts, nvidia_counts = get_overall_stats(df, available_models) | |
| amd_failure_rate = (amd_counts[1] / sum(amd_counts)) if sum(amd_counts) > 0 else 0.0 | |
| amd_failure_rate *= 100 | |
| nvidia_failure_rate = (nvidia_counts[1] / sum(nvidia_counts)) if sum(nvidia_counts) > 0 else 0.0 | |
| nvidia_failure_rate *= 100 | |
| # Calculate dimensions for N-column layout | |
| model_count = len(available_models) | |
| rows = (model_count + COLUMNS - 1) // COLUMNS # Ceiling division | |
| # Figure dimensions - wider for columns, height based on rows | |
| height_per_row = min(MIN_HEIGHT_PER_ROW, MAX_HEIGHT / max(rows, 1)) | |
| figure_height = min(MAX_HEIGHT, rows * height_per_row + FIGURE_PADDING) | |
| fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, figure_height), facecolor='#000000') | |
| ax.set_facecolor('#000000') | |
| # Add overall failure rates at the top as a proper title | |
| failure_text = f"Overall Failure Rates: AMD {amd_failure_rate:.1f}% | NVIDIA {nvidia_failure_rate:.1f}%" | |
| ax.text(50, -1.25, failure_text, ha='center', va='top', | |
| color='#FFFFFF', fontsize=FAILURE_RATE_FONT_SIZE, | |
| fontfamily='monospace', fontweight='bold') | |
| visible_model_count = 0 | |
| max_y = 0 | |
| for i, model_name in enumerate(available_models): | |
| if model_name not in df.index: | |
| continue | |
| row = df.loc[model_name] | |
| # Extract and process model data | |
| amd_stats, nvidia_stats = extract_model_data(row)[:2] | |
| # Calculate position in 4-column grid | |
| col = visible_model_count % COLUMNS | |
| row = visible_model_count // COLUMNS | |
| # Calculate horizontal position for this column | |
| col_left = col * COLUMN_WIDTH + BAR_MARGIN | |
| col_center = col * COLUMN_WIDTH + COLUMN_WIDTH / 2 | |
| # Calculate vertical position for this row - start from top | |
| vertical_spacing = height_per_row | |
| y_base = (VERTICAL_SPACING_RATIO + row) * vertical_spacing | |
| y_model_name = y_base # Model name above AMD bar | |
| y_amd_bar = y_base + vertical_spacing * AMD_BAR_OFFSET # AMD bar | |
| y_nvidia_bar = y_base + vertical_spacing * NVIDIA_BAR_OFFSET # NVIDIA bar | |
| max_y = max(max_y, y_nvidia_bar + vertical_spacing * 0.3) | |
| # Model name centered above the bars in this column | |
| ax.text(col_center, y_model_name, model_name.lower(), | |
| ha='center', va='center', color='#FFFFFF', | |
| fontsize=MODEL_NAME_FONT_SIZE, fontfamily='monospace', fontweight='bold') | |
| # AMD label and bar in this column | |
| bar_height = min(0.4, vertical_spacing * BAR_HEIGHT_RATIO) | |
| # Draw AMD bar | |
| draw_text_and_bar("amd", amd_stats, y_amd_bar, col_left, bar_height, ax) | |
| # Draw NVIDIA bar | |
| draw_text_and_bar("nvidia", nvidia_stats, y_nvidia_bar, col_left, bar_height, ax) | |
| # Increment counter for next visible model | |
| visible_model_count += 1 | |
| # Add AMD and NVIDIA test totals in the bottom left | |
| # Calculate line spacing to align middle with legend | |
| line_height = 0.4 # Height between lines | |
| legend_y = max_y + 1 | |
| # Position the two lines so their middle aligns with legend_y | |
| amd_y = legend_y - line_height / 2 | |
| nvidia_y = legend_y + line_height / 2 | |
| amd_totals_text = f"AMD Tests - Passed: {amd_counts[0]}, Failed: {amd_counts[1]}, Skipped: {amd_counts[2]}" | |
| nvidia_totals_text = f"NVIDIA Tests - Passed: {nvidia_counts[0]}, Failed: {nvidia_counts[1]}, Skipped: {nvidia_counts[2]}" | |
| ax.text(0, amd_y, amd_totals_text, | |
| ha='left', va='bottom', color='#CCCCCC', | |
| fontsize=14, fontfamily='monospace') | |
| ax.text(0, nvidia_y, nvidia_totals_text, | |
| ha='left', va='bottom', color='#CCCCCC', | |
| fontsize=14, fontfamily='monospace') | |
| # Add legend horizontally in bottom right corner | |
| patch_height = 0.3 | |
| patch_width = 3 | |
| legend_start_x = 68.7 | |
| legend_y = max_y + 1 | |
| legend_spacing = 10 | |
| legend_font_size = 15 | |
| # Legend entries | |
| legend_items = [ | |
| ('passed', 'Passed'), | |
| ('failed', 'Failed'), | |
| ('skipped', 'Skipped'), | |
| ] | |
| for i, (status, label) in enumerate(legend_items): | |
| x_pos = legend_start_x + i * legend_spacing | |
| # Small colored square | |
| ax.add_patch(plt.Rectangle((x_pos - 0.6, legend_y), patch_width, -patch_height, | |
| facecolor=COLORS[status], alpha=0.9)) | |
| # Status label | |
| ax.text(x_pos + patch_width, legend_y, label, | |
| ha='left', va='bottom', color='#CCCCCC', | |
| fontsize=legend_font_size, fontfamily='monospace') | |
| # Style the axes to be completely invisible and span full width | |
| ax.set_xlim(-5, 105) # Slightly wider to accommodate labels | |
| ax.set_ylim(0, max_y + 1) # Add some padding at the top for title | |
| ax.set_xlabel('') | |
| ax.set_ylabel('') | |
| ax.spines['bottom'].set_visible(False) | |
| ax.spines['left'].set_visible(False) | |
| ax.spines['top'].set_visible(False) | |
| ax.spines['right'].set_visible(False) | |
| ax.set_xticks([]) | |
| ax.set_yticks([]) | |
| ax.yaxis.set_inverted(True) | |
| # Remove all margins to make figure stick to top | |
| plt.tight_layout() | |
| return fig | |