diff --git a/graph.py b/graph.py index 930b9f03d..9caf11152 100644 --- a/graph.py +++ b/graph.py @@ -9,6 +9,9 @@ import matplotlib.pyplot as plt import pandas as pd import seaborn as sns +from graph_common import (COLORS, setup_theme, apply_smoothing, style_axes, + add_title, style_legend, add_reference_lines) + if len(sys.argv) <= 2: print("graph.py: ") sys.exit() @@ -22,18 +25,8 @@ df.index = pd.to_datetime(df.index, utc=True) print(df) -# Set modern Seaborn style with enhanced settings -sns.set_theme(style="ticks", context="talk", palette="muted") -plt.rcParams.update({ - 'font.family': 'sans-serif', - 'font.sans-serif': ['Inter', 'SF Pro Display', 'Segoe UI', 'Arial'], - 'axes.facecolor': '#FAFAFA', - 'figure.facecolor': 'white', - 'axes.edgecolor': '#CCCCCC', - 'axes.linewidth': 0.8, - 'xtick.color': '#555555', - 'ytick.color': '#555555', -}) +# Set up modern theme +setup_theme() # Create figure with better proportions and higher DPI fig, ax = plt.subplots(figsize=(18, 9), dpi=100) @@ -53,18 +46,10 @@ df_plot_long = df_plot.melt(id_vars='date', var_name='metric', value_name='count df_plot_long['count'] = pd.to_numeric(df_plot_long['count'], errors='coerce') # Apply smoothing using rolling average (window of 15 for smoother lines) -df_plot_long['count_smooth'] = df_plot_long.groupby('metric')['count'].transform( - lambda x: x.rolling(window=15, min_periods=1, center=True).mean() -) +df_plot_long['count_smooth'] = apply_smoothing(df_plot_long, 'metric', 'count') -# Modern vibrant color palette -palette = { - 'total': '#0066CC', # Vibrant blue - 'pass': '#10B981', # Modern green (Tailwind emerald) - 'fail': '#EF4444', # Modern red (Tailwind red) - 'error': '#F59E0B', # Modern amber - 'skip': '#8B5CF6' # Modern purple (Tailwind violet) -} +# Use color palette from common module +palette = {k: COLORS[k] for k in ['total', 'pass', 'fail', 'error', 'skip']} # Add gradient-like area fills first (behind lines) for metric in ['total', 'pass', 'fail']: @@ -87,62 +72,21 @@ sns.lineplot( zorder=3 ) -# Modern title with better spacing -ax.text(0.5, 1.12, f'uutils coreutils — {title} Test Suite Results', - ha='center', va='bottom', transform=ax.transAxes, - fontsize=26, fontweight='bold', family='sans-serif', color='#1a1a1a') -ax.text(0.5, 1.06, 'Tracking test results over time to measure progress and compatibility', - ha='center', va='bottom', transform=ax.transAxes, - fontsize=13, color='#6B7280', style='italic', alpha=0.9) +# Add title and subtitle +add_title(ax, f'uutils coreutils — {title} Test Suite Results', + 'Tracking test results over time to measure progress and compatibility') -ax.set_xlabel('Date', fontsize=15, fontweight='600', labelpad=15, color='#374151') -ax.set_ylabel('Number of Tests', fontsize=15, fontweight='600', labelpad=15, color='#374151') +# Style axes with labels and grid +style_axes(ax, xlabel='Date', ylabel='Number of Tests') -# Modern grid styling with better contrast -ax.grid(True, alpha=0.35, linestyle='-', linewidth=1, color='#E5E7EB', which='major', zorder=0) -ax.grid(True, alpha=0.15, linestyle=':', linewidth=0.5, color='#F3F4F6', which='minor', zorder=0) -ax.set_axisbelow(True) -ax.minorticks_on() - -# Add subtle horizontal reference lines at key values +# Add reference lines y_max = df_plot_long['count_smooth'].max() -if y_max > 100: - ax.axhline(y=y_max * 0.75, color='#D1D5DB', linestyle='--', linewidth=0.8, alpha=0.3, zorder=0) - ax.axhline(y=y_max * 0.5, color='#D1D5DB', linestyle='--', linewidth=0.8, alpha=0.3, zorder=0) +add_reference_lines(ax, y_max) -# Modern legend styling +# Style legend handles, labels = ax.get_legend_handles_labels() -# Capitalize labels labels = [label.capitalize() for label in labels] -legend = ax.legend(handles, labels, loc='upper left', frameon=True, - fancybox=False, shadow=False, ncol=len(plot_columns), - bbox_to_anchor=(0, 1.04), fontsize=13, - edgecolor='#D1D5DB', framealpha=0.98, - borderpad=1.2, labelspacing=0.8, columnspacing=2) -legend.get_frame().set_facecolor('#FFFFFF') -legend.get_frame().set_linewidth(2) -legend.get_frame().set_boxstyle('round,pad=0.5') - -# Format x-axis dates -plt.xticks(rotation=45, ha='right') -ax.margins(x=0.01) -ax.set_ylim(bottom=0) - -# Modern spine styling -sns.despine(ax=ax, top=True, right=True, left=False, bottom=False, offset=8) -ax.spines['left'].set_linewidth(2) -ax.spines['bottom'].set_linewidth(2) -ax.spines['left'].set_color('#9CA3AF') -ax.spines['bottom'].set_color('#9CA3AF') - -# Modern gradient-like background -ax.patch.set_facecolor('#FAFAFA') -ax.patch.set_alpha(0.6) - -# Add a subtle shadow effect -ax.add_patch(plt.Rectangle((0, 0), 1, 1, transform=ax.transAxes, - facecolor='none', edgecolor='#E5E7EB', - linewidth=3, zorder=-1)) +style_legend(ax, handles, labels, ncol=len(plot_columns), loc='upper left') # Tight layout plt.tight_layout() diff --git a/graph_common.py b/graph_common.py new file mode 100644 index 000000000..5ad762db0 --- /dev/null +++ b/graph_common.py @@ -0,0 +1,151 @@ +# This file is part of the uutils coreutils package. +# +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. + +"""Common styling and utilities for graph generation.""" + +import matplotlib.pyplot as plt +import seaborn as sns + + +# Modern vibrant color palette +COLORS = { + 'total': '#0066CC', # Vibrant blue + 'pass': '#10B981', # Modern green (Tailwind emerald) + 'fail': '#EF4444', # Modern red (Tailwind red) + 'error': '#F59E0B', # Modern amber + 'skip': '#8B5CF6', # Modern purple (Tailwind violet) + 'size': '#6366F1', # Modern indigo + 'multisize': '#10B981', # Modern green (Tailwind emerald) + 'default': '#10B981', # Modern green (Tailwind emerald) +} + + +def setup_theme(): + """Set up modern Seaborn theme with enhanced settings.""" + sns.set_theme(style="ticks", context="talk", palette="muted") + plt.rcParams.update({ + 'font.family': 'sans-serif', + 'font.sans-serif': ['Inter', 'SF Pro Display', 'Segoe UI', 'Arial'], + 'axes.facecolor': '#FAFAFA', + 'figure.facecolor': 'white', + 'axes.edgecolor': '#CCCCCC', + 'axes.linewidth': 0.8, + 'xtick.color': '#555555', + 'ytick.color': '#555555', + }) + + +def apply_smoothing(df, group_col, value_col, window=15): + """Apply rolling average smoothing to data. + + Args: + df: DataFrame with data to smooth + group_col: Column name to group by (or None for no grouping) + value_col: Column name containing values to smooth + window: Rolling window size (default: 15) + + Returns: + Series with smoothed values + """ + if group_col: + return df.groupby(group_col)[value_col].transform( + lambda x: x.rolling(window=window, min_periods=1, center=True).mean() + ) + else: + return df[value_col].rolling(window=window, min_periods=1, center=True).mean() + + +def style_axes(ax, xlabel='Date', ylabel='Value'): + """Apply modern styling to axes. + + Args: + ax: Matplotlib axes object + xlabel: Label for x-axis + ylabel: Label for y-axis + """ + ax.set_xlabel(xlabel, fontsize=15, fontweight='600', labelpad=15, color='#374151') + ax.set_ylabel(ylabel, fontsize=15, fontweight='600', labelpad=15, color='#374151') + + # Modern grid styling + ax.grid(True, alpha=0.35, linestyle='-', linewidth=1, color='#E5E7EB', which='major', zorder=0) + ax.grid(True, alpha=0.15, linestyle=':', linewidth=0.5, color='#F3F4F6', which='minor', zorder=0) + ax.set_axisbelow(True) + ax.minorticks_on() + + # Format x-axis dates + plt.xticks(rotation=45, ha='right') + ax.margins(x=0.01) + ax.set_ylim(bottom=0) + + # Modern spine styling + sns.despine(ax=ax, top=True, right=True, left=False, bottom=False, offset=8) + ax.spines['left'].set_linewidth(2) + ax.spines['bottom'].set_linewidth(2) + ax.spines['left'].set_color('#9CA3AF') + ax.spines['bottom'].set_color('#9CA3AF') + + # Modern background + ax.patch.set_facecolor('#FAFAFA') + ax.patch.set_alpha(0.6) + + # Add subtle shadow effect + ax.add_patch(plt.Rectangle((0, 0), 1, 1, transform=ax.transAxes, + facecolor='none', edgecolor='#E5E7EB', + linewidth=3, zorder=-1)) + + +def add_title(ax, title, subtitle=None): + """Add modern title and optional subtitle to axes. + + Args: + ax: Matplotlib axes object + title: Main title text + subtitle: Optional subtitle text + """ + ax.text(0.5, 1.12, title, + ha='center', va='bottom', transform=ax.transAxes, + fontsize=26, fontweight='bold', family='sans-serif', color='#1a1a1a') + + if subtitle: + ax.text(0.5, 1.06, subtitle, + ha='center', va='bottom', transform=ax.transAxes, + fontsize=13, color='#6B7280', style='italic', alpha=0.9) + + +def style_legend(ax, handles, labels, ncol=1, loc='upper left'): + """Apply modern styling to legend. + + Args: + ax: Matplotlib axes object + handles: Legend handles + labels: Legend labels + ncol: Number of columns (default: 1) + loc: Legend location (default: 'upper left') + + Returns: + Legend object + """ + legend = ax.legend(handles, labels, loc=loc, frameon=True, + fancybox=False, shadow=False, ncol=ncol, + bbox_to_anchor=(0, 1.04) if 'left' in loc else (1, 1.04), + fontsize=13, + edgecolor='#D1D5DB', framealpha=0.98, + borderpad=1.2, labelspacing=0.8, columnspacing=2) + legend.get_frame().set_facecolor('#FFFFFF') + legend.get_frame().set_linewidth(2) + legend.get_frame().set_boxstyle('round,pad=0.5') + return legend + + +def add_reference_lines(ax, y_max): + """Add horizontal reference lines at key values. + + Args: + ax: Matplotlib axes object + y_max: Maximum y value for calculating reference positions + """ + if y_max > 100: + ax.axhline(y=y_max * 0.75, color='#D1D5DB', linestyle='--', linewidth=0.8, alpha=0.3, zorder=0) + ax.axhline(y=y_max * 0.5, color='#D1D5DB', linestyle='--', linewidth=0.8, alpha=0.3, zorder=0) diff --git a/individual-size-graph.py b/individual-size-graph.py index f02740cde..63ecdbcb3 100644 --- a/individual-size-graph.py +++ b/individual-size-graph.py @@ -10,26 +10,19 @@ import matplotlib.pyplot as plt import pandas as pd import seaborn as sns +from graph_common import (COLORS, setup_theme, apply_smoothing, style_axes, + add_title) + df = pd.read_json(sys.argv[1], orient="index") df.index = pd.to_datetime(df.index, utc=True) Path("individual-size-results").mkdir(exist_ok=True) -# Set modern Seaborn style with enhanced settings -sns.set_theme(style="ticks", context="talk", palette="muted") -plt.rcParams.update({ - 'font.family': 'sans-serif', - 'font.sans-serif': ['Inter', 'SF Pro Display', 'Segoe UI', 'Arial'], - 'axes.facecolor': '#FAFAFA', - 'figure.facecolor': 'white', - 'axes.edgecolor': '#CCCCCC', - 'axes.linewidth': 0.8, - 'xtick.color': '#555555', - 'ytick.color': '#555555', -}) +# Set up modern theme +setup_theme() -# Modern color for size evolution -size_color = '#10B981' # Modern green (Tailwind emerald) +# Use color from common module +size_color = COLORS['default'] for name, series in df["sizes"].apply(pd.Series).items(): # Filter out None values which indicate missing data for 'name' @@ -45,11 +38,11 @@ for name, series in df["sizes"].apply(pd.Series).items(): # Prepare data for Seaborn plot_data = pd.DataFrame({ 'date': sizes.index, - 'size': pd.to_numeric(sizes['size'], errors='coerce') + 'size': pd.to_numeric(sizes.values, errors='coerce') }) # Apply smoothing using rolling average - plot_data['size_smooth'] = plot_data['size'].rolling(window=15, min_periods=1, center=True).mean() + plot_data['size_smooth'] = apply_smoothing(plot_data, None, 'size') # Add gradient-like area fill first ax.fill_between(plot_data['date'], 0, plot_data['size'], @@ -68,43 +61,12 @@ for name, series in df["sizes"].apply(pd.Series).items(): zorder=3 ) - # Modern title - ax.text(0.5, 1.12, f'uutils coreutils — "{name}" Binary Size', - ha='center', va='bottom', transform=ax.transAxes, - fontsize=26, fontweight='bold', family='sans-serif', color='#1a1a1a') - ax.text(0.5, 1.06, f'Individual utility size tracking over development history', - ha='center', va='bottom', transform=ax.transAxes, - fontsize=13, color='#6B7280', style='italic', alpha=0.9) + # Add title and subtitle + add_title(ax, f'uutils coreutils — "{name}" Binary Size', + 'Individual utility size tracking over development history') - ax.set_xlabel('Date', fontsize=15, fontweight='600', labelpad=15, color='#374151') - ax.set_ylabel('Size (kilobytes)', fontsize=15, fontweight='600', labelpad=15, color='#374151') - - # Modern grid styling - ax.grid(True, alpha=0.35, linestyle='-', linewidth=1, color='#E5E7EB', which='major', zorder=0) - ax.grid(True, alpha=0.15, linestyle=':', linewidth=0.5, color='#F3F4F6', which='minor', zorder=0) - ax.set_axisbelow(True) - ax.minorticks_on() - - # Format x-axis dates - plt.xticks(rotation=45, ha='right') - ax.margins(x=0.01) - ax.set_ylim(bottom=0) - - # Modern spine styling - sns.despine(ax=ax, top=True, right=True, left=False, bottom=False, offset=8) - ax.spines['left'].set_linewidth(2) - ax.spines['bottom'].set_linewidth(2) - ax.spines['left'].set_color('#9CA3AF') - ax.spines['bottom'].set_color('#9CA3AF') - - # Modern background - ax.patch.set_facecolor('#FAFAFA') - ax.patch.set_alpha(0.6) - - # Add subtle shadow effect - ax.add_patch(plt.Rectangle((0, 0), 1, 1, transform=ax.transAxes, - facecolor='none', edgecolor='#E5E7EB', - linewidth=3, zorder=-1)) + # Style axes with labels and grid + style_axes(ax, xlabel='Date', ylabel='Size (kilobytes)') # Tight layout plt.tight_layout() diff --git a/size-graph.py b/size-graph.py index fc470adc9..cbaa9f462 100644 --- a/size-graph.py +++ b/size-graph.py @@ -9,6 +9,9 @@ import matplotlib.pyplot as plt import pandas as pd import seaborn as sns +from graph_common import (COLORS, setup_theme, apply_smoothing, style_axes, + add_title, style_legend) + d = pd.read_json(sys.argv[1], orient="index") df = pd.DataFrame(d) @@ -17,18 +20,8 @@ df.index = pd.to_datetime(df.index, utc=True, format="mixed") print(df) -# Set modern Seaborn style with enhanced settings -sns.set_theme(style="ticks", context="talk", palette="muted") -plt.rcParams.update({ - 'font.family': 'sans-serif', - 'font.sans-serif': ['Inter', 'SF Pro Display', 'Segoe UI', 'Arial'], - 'axes.facecolor': '#FAFAFA', - 'figure.facecolor': 'white', - 'axes.edgecolor': '#CCCCCC', - 'axes.linewidth': 0.8, - 'xtick.color': '#555555', - 'ytick.color': '#555555', -}) +# Set up modern theme +setup_theme() # Create figure with better proportions and higher DPI fig, ax = plt.subplots(figsize=(18, 9), dpi=100) @@ -43,14 +36,12 @@ df_plot_long = df_plot.melt(id_vars='date', var_name='binary_type', value_name=' df_plot_long['size_kb'] = pd.to_numeric(df_plot_long['size_kb'], errors='coerce') # Apply smoothing using rolling average -df_plot_long['size_kb_smooth'] = df_plot_long.groupby('binary_type')['size_kb'].transform( - lambda x: x.rolling(window=15, min_periods=1, center=True).mean() -) +df_plot_long['size_kb_smooth'] = apply_smoothing(df_plot_long, 'binary_type', 'size_kb') -# Modern color palette +# Use color palette from common module palette = { - 'size': '#6366F1', # Modern indigo - 'multisize': '#10B981' # Modern green (Tailwind emerald) + 'size': COLORS['size'], + 'multisize': COLORS['multisize'] } # Add gradient-like area fills first @@ -74,59 +65,21 @@ sns.lineplot( zorder=3 ) -# Modern title -ax.text(0.5, 1.12, 'uutils coreutils — Binary Size Evolution', - ha='center', va='bottom', transform=ax.transAxes, - fontsize=26, fontweight='bold', family='sans-serif', color='#1a1a1a') -ax.text(0.5, 1.06, 'Tracking binary size optimization and comparing build strategies', - ha='center', va='bottom', transform=ax.transAxes, - fontsize=13, color='#6B7280', style='italic', alpha=0.9) +# Add title and subtitle +add_title(ax, 'uutils coreutils — Binary Size Evolution', + 'Tracking binary size optimization and comparing build strategies') -ax.set_xlabel('Date', fontsize=15, fontweight='600', labelpad=15, color='#374151') -ax.set_ylabel('Size (kilobytes)', fontsize=15, fontweight='600', labelpad=15, color='#374151') +# Style axes with labels and grid +style_axes(ax, xlabel='Date', ylabel='Size (kilobytes)') -# Modern grid styling -ax.grid(True, alpha=0.35, linestyle='-', linewidth=1, color='#E5E7EB', which='major', zorder=0) -ax.grid(True, alpha=0.15, linestyle=':', linewidth=0.5, color='#F3F4F6', which='minor', zorder=0) -ax.set_axisbelow(True) -ax.minorticks_on() - -# Modern legend +# Style legend handles, labels = ax.get_legend_handles_labels() label_map = { 'size': 'Multiple Binaries', 'multisize': 'Multicall Binary (Optimized)' } labels = [label_map.get(label, label) for label in labels] -legend = ax.legend(handles, labels, loc='upper left', frameon=True, - fancybox=False, shadow=False, fontsize=13, - bbox_to_anchor=(0, 1.04), - edgecolor='#D1D5DB', framealpha=0.98, - borderpad=1.2, labelspacing=0.8) -legend.get_frame().set_facecolor('#FFFFFF') -legend.get_frame().set_linewidth(2) -legend.get_frame().set_boxstyle('round,pad=0.5') - -# Format x-axis dates -plt.xticks(rotation=45, ha='right') -ax.margins(x=0.01) -ax.set_ylim(bottom=0) - -# Modern spine styling -sns.despine(ax=ax, top=True, right=True, left=False, bottom=False, offset=8) -ax.spines['left'].set_linewidth(2) -ax.spines['bottom'].set_linewidth(2) -ax.spines['left'].set_color('#9CA3AF') -ax.spines['bottom'].set_color('#9CA3AF') - -# Modern background -ax.patch.set_facecolor('#FAFAFA') -ax.patch.set_alpha(0.6) - -# Add subtle shadow effect -ax.add_patch(plt.Rectangle((0, 0), 1, 1, transform=ax.transAxes, - facecolor='none', edgecolor='#E5E7EB', - linewidth=3, zorder=-1)) +style_legend(ax, handles, labels, ncol=1, loc='upper left') # Tight layout plt.tight_layout()