| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- #!/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()
|