#!/usr/bin/env python3 """ Matplotlib Style Configurator Interactive utility to configure matplotlib style preferences and generate custom style sheets. Creates a preview of the style and optionally saves it as a .mplstyle file. Usage: python style_configurator.py [--preset PRESET] [--output FILE] [--preview] Presets: publication, presentation, web, dark, minimal """ import numpy as np import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import argparse import os # Predefined style presets STYLE_PRESETS = { 'publication': { 'figure.figsize': (8, 6), 'figure.dpi': 100, 'savefig.dpi': 300, 'savefig.bbox': 'tight', 'font.family': 'sans-serif', 'font.sans-serif': ['Arial', 'Helvetica'], 'font.size': 11, 'axes.labelsize': 12, 'axes.titlesize': 14, 'axes.linewidth': 1.5, 'axes.grid': False, 'axes.spines.top': False, 'axes.spines.right': False, 'lines.linewidth': 2, 'lines.markersize': 8, 'xtick.labelsize': 10, 'ytick.labelsize': 10, 'xtick.direction': 'in', 'ytick.direction': 'in', 'xtick.major.size': 6, 'ytick.major.size': 6, 'xtick.major.width': 1.5, 'ytick.major.width': 1.5, 'legend.fontsize': 10, 'legend.frameon': True, 'legend.framealpha': 1.0, 'legend.edgecolor': 'black', }, 'presentation': { 'figure.figsize': (12, 8), 'figure.dpi': 100, 'savefig.dpi': 150, 'font.size': 16, 'axes.labelsize': 20, 'axes.titlesize': 24, 'axes.linewidth': 2, 'lines.linewidth': 3, 'lines.markersize': 12, 'xtick.labelsize': 16, 'ytick.labelsize': 16, 'legend.fontsize': 16, 'axes.grid': True, 'grid.alpha': 0.3, }, 'web': { 'figure.figsize': (10, 6), 'figure.dpi': 96, 'savefig.dpi': 150, 'font.size': 11, 'axes.labelsize': 12, 'axes.titlesize': 14, 'lines.linewidth': 2, 'axes.grid': True, 'grid.alpha': 0.2, 'grid.linestyle': '--', }, 'dark': { 'figure.facecolor': '#1e1e1e', 'figure.edgecolor': '#1e1e1e', 'axes.facecolor': '#1e1e1e', 'axes.edgecolor': 'white', 'axes.labelcolor': 'white', 'text.color': 'white', 'xtick.color': 'white', 'ytick.color': 'white', 'grid.color': 'gray', 'grid.alpha': 0.3, 'axes.grid': True, 'legend.facecolor': '#1e1e1e', 'legend.edgecolor': 'white', 'savefig.facecolor': '#1e1e1e', }, 'minimal': { 'figure.figsize': (10, 6), 'axes.spines.top': False, 'axes.spines.right': False, 'axes.spines.left': False, 'axes.spines.bottom': False, 'axes.grid': False, 'xtick.bottom': True, 'ytick.left': True, 'axes.axisbelow': True, 'lines.linewidth': 2.5, 'font.size': 12, } } def generate_preview_data(): """Generate sample data for style preview.""" np.random.seed(42) x = np.linspace(0, 10, 100) y1 = np.sin(x) + 0.1 * np.random.randn(100) y2 = np.cos(x) + 0.1 * np.random.randn(100) scatter_x = np.random.randn(100) scatter_y = 2 * scatter_x + np.random.randn(100) categories = ['A', 'B', 'C', 'D', 'E'] bar_values = [25, 40, 30, 55, 45] return { 'x': x, 'y1': y1, 'y2': y2, 'scatter_x': scatter_x, 'scatter_y': scatter_y, 'categories': categories, 'bar_values': bar_values } def create_style_preview(style_dict=None): """Create a preview figure demonstrating the style.""" if style_dict: plt.rcParams.update(style_dict) data = generate_preview_data() fig = plt.figure(figsize=(14, 10)) gs = GridSpec(2, 2, figure=fig, hspace=0.3, wspace=0.3) # Line plot ax1 = fig.add_subplot(gs[0, 0]) ax1.plot(data['x'], data['y1'], label='sin(x)', marker='o', markevery=10) ax1.plot(data['x'], data['y2'], label='cos(x)', linestyle='--') ax1.set_xlabel('X axis') ax1.set_ylabel('Y axis') ax1.set_title('Line Plot') ax1.legend() ax1.grid(True, alpha=0.3) # Scatter plot ax2 = fig.add_subplot(gs[0, 1]) colors = np.sqrt(data['scatter_x']**2 + data['scatter_y']**2) scatter = ax2.scatter(data['scatter_x'], data['scatter_y'], c=colors, cmap='viridis', alpha=0.6, s=50) ax2.set_xlabel('X axis') ax2.set_ylabel('Y axis') ax2.set_title('Scatter Plot') cbar = plt.colorbar(scatter, ax=ax2) cbar.set_label('Distance') ax2.grid(True, alpha=0.3) # Bar chart ax3 = fig.add_subplot(gs[1, 0]) bars = ax3.bar(data['categories'], data['bar_values'], edgecolor='black', linewidth=1) # Color bars with gradient colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(bars))) for bar, color in zip(bars, colors): bar.set_facecolor(color) ax3.set_xlabel('Categories') ax3.set_ylabel('Values') ax3.set_title('Bar Chart') ax3.grid(True, axis='y', alpha=0.3) # Multiple line plot with fills ax4 = fig.add_subplot(gs[1, 1]) ax4.plot(data['x'], data['y1'], label='Signal 1', linewidth=2) ax4.fill_between(data['x'], data['y1'] - 0.2, data['y1'] + 0.2, alpha=0.3, label='±1 std') ax4.plot(data['x'], data['y2'], label='Signal 2', linewidth=2) ax4.fill_between(data['x'], data['y2'] - 0.2, data['y2'] + 0.2, alpha=0.3) ax4.set_xlabel('X axis') ax4.set_ylabel('Y axis') ax4.set_title('Time Series with Uncertainty') ax4.legend() ax4.grid(True, alpha=0.3) fig.suptitle('Style Preview', fontsize=16, fontweight='bold') return fig def save_style_file(style_dict, filename): """Save style dictionary as .mplstyle file.""" with open(filename, 'w') as f: f.write("# Custom matplotlib style\n") f.write("# Generated by style_configurator.py\n\n") # Group settings by category categories = { 'Figure': ['figure.'], 'Font': ['font.'], 'Axes': ['axes.'], 'Lines': ['lines.'], 'Markers': ['markers.'], 'Ticks': ['tick.', 'xtick.', 'ytick.'], 'Grid': ['grid.'], 'Legend': ['legend.'], 'Savefig': ['savefig.'], 'Text': ['text.'], } for category, prefixes in categories.items(): category_items = {k: v for k, v in style_dict.items() if any(k.startswith(p) for p in prefixes)} if category_items: f.write(f"# {category}\n") for key, value in sorted(category_items.items()): # Format value appropriately if isinstance(value, (list, tuple)): value_str = ', '.join(str(v) for v in value) elif isinstance(value, bool): value_str = str(value) else: value_str = str(value) f.write(f"{key}: {value_str}\n") f.write("\n") print(f"Style saved to {filename}") def print_style_info(style_dict): """Print information about the style.""" print("\n" + "="*60) print("STYLE CONFIGURATION") print("="*60) categories = { 'Figure Settings': ['figure.'], 'Font Settings': ['font.'], 'Axes Settings': ['axes.'], 'Line Settings': ['lines.'], 'Grid Settings': ['grid.'], 'Legend Settings': ['legend.'], } for category, prefixes in categories.items(): category_items = {k: v for k, v in style_dict.items() if any(k.startswith(p) for p in prefixes)} if category_items: print(f"\n{category}:") for key, value in sorted(category_items.items()): print(f" {key}: {value}") print("\n" + "="*60 + "\n") def list_available_presets(): """Print available style presets.""" print("\nAvailable style presets:") print("-" * 40) descriptions = { 'publication': 'Optimized for academic publications', 'presentation': 'Large fonts for presentations', 'web': 'Optimized for web display', 'dark': 'Dark background theme', 'minimal': 'Minimal, clean style', } for preset, desc in descriptions.items(): print(f" {preset:15s} - {desc}") print("-" * 40 + "\n") def interactive_mode(): """Run interactive mode to customize style settings.""" print("\n" + "="*60) print("MATPLOTLIB STYLE CONFIGURATOR - Interactive Mode") print("="*60) list_available_presets() preset = input("Choose a preset to start from (or 'custom' for default): ").strip().lower() if preset in STYLE_PRESETS: style_dict = STYLE_PRESETS[preset].copy() print(f"\nStarting from '{preset}' preset") else: style_dict = {} print("\nStarting from default matplotlib style") print("\nCommon settings you might want to customize:") print(" 1. Figure size") print(" 2. Font sizes") print(" 3. Line widths") print(" 4. Grid settings") print(" 5. Color scheme") print(" 6. Done, show preview") while True: choice = input("\nSelect option (1-6): ").strip() if choice == '1': width = input(" Figure width (inches, default 10): ").strip() or '10' height = input(" Figure height (inches, default 6): ").strip() or '6' style_dict['figure.figsize'] = (float(width), float(height)) elif choice == '2': base = input(" Base font size (default 12): ").strip() or '12' style_dict['font.size'] = float(base) style_dict['axes.labelsize'] = float(base) + 2 style_dict['axes.titlesize'] = float(base) + 4 elif choice == '3': lw = input(" Line width (default 2): ").strip() or '2' style_dict['lines.linewidth'] = float(lw) elif choice == '4': grid = input(" Enable grid? (y/n): ").strip().lower() style_dict['axes.grid'] = grid == 'y' if style_dict['axes.grid']: alpha = input(" Grid transparency (0-1, default 0.3): ").strip() or '0.3' style_dict['grid.alpha'] = float(alpha) elif choice == '5': print(" Theme options: 1=Light, 2=Dark") theme = input(" Select theme (1-2): ").strip() if theme == '2': style_dict.update(STYLE_PRESETS['dark']) elif choice == '6': break return style_dict def main(): """Main function.""" parser = argparse.ArgumentParser( description='Matplotlib style configurator', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Show available presets python style_configurator.py --list # Preview a preset python style_configurator.py --preset publication --preview # Save a preset as .mplstyle file python style_configurator.py --preset publication --output my_style.mplstyle # Interactive mode python style_configurator.py --interactive """ ) parser.add_argument('--preset', type=str, choices=list(STYLE_PRESETS.keys()), help='Use a predefined style preset') parser.add_argument('--output', type=str, help='Save style to .mplstyle file') parser.add_argument('--preview', action='store_true', help='Show style preview') parser.add_argument('--list', action='store_true', help='List available presets') parser.add_argument('--interactive', action='store_true', help='Run in interactive mode') args = parser.parse_args() if args.list: list_available_presets() # Also show currently available matplotlib styles print("\nBuilt-in matplotlib styles:") print("-" * 40) for style in sorted(plt.style.available): print(f" {style}") return if args.interactive: style_dict = interactive_mode() elif args.preset: style_dict = STYLE_PRESETS[args.preset].copy() print(f"Using '{args.preset}' preset") else: print("No preset or interactive mode specified. Showing default preview.") style_dict = {} if style_dict: print_style_info(style_dict) if args.output: save_style_file(style_dict, args.output) if args.preview or args.interactive: print("Creating style preview...") fig = create_style_preview(style_dict if style_dict else None) if args.output: preview_filename = args.output.replace('.mplstyle', '_preview.png') plt.savefig(preview_filename, dpi=150, bbox_inches='tight') print(f"Preview saved to {preview_filename}") plt.show() if __name__ == "__main__": main()