""" Visualization theme configuration for consistent dark theme styling across all plots. """ from typing import Dict import plotly.graph_objects as go from config.constants import PLOT_WIDTH, PLOT_HEIGHT, PLOT_COLORSCALE, PLOT_BG_COLOR # Dark theme colors DARK_THEME = { 'background': 'rgb(20,20,20)', 'paper': 'rgb(20,20,20)', 'plot_bg': 'rgb(30,30,30)', 'grid': 'rgb(50,50,50)', 'text': 'rgb(220,220,220)', 'primary': '#00D9FF', # Cyan 'secondary': '#FF00FF', # Magenta 'success': '#00FF88', # Green 'warning': '#FFD700', # Gold 'danger': '#FF4444', # Red 'neutral': 'rgb(150,150,150)' } # Colorscales COLORSCALES = { 'viridis': 'Viridis', 'plasma': 'Plasma', 'inferno': 'Inferno', 'magma': 'Magma', 'cividis': 'Cividis', 'turbo': 'Turbo', 'rainbow': 'Rainbow', 'jet': 'Jet', 'portland': 'Portland', 'picnic': 'Picnic' } # Default layout template DEFAULT_LAYOUT = { 'paper_bgcolor': DARK_THEME['background'], 'plot_bgcolor': DARK_THEME['plot_bg'], 'font': { 'color': DARK_THEME['text'], 'family': 'Arial, sans-serif', 'size': 12 }, 'title': { 'font': { 'size': 18, 'color': DARK_THEME['text'] } }, 'xaxis': { 'gridcolor': DARK_THEME['grid'], 'zerolinecolor': DARK_THEME['grid'], 'color': DARK_THEME['text'] }, 'yaxis': { 'gridcolor': DARK_THEME['grid'], 'zerolinecolor': DARK_THEME['grid'], 'color': DARK_THEME['text'] }, 'width': PLOT_WIDTH, 'height': PLOT_HEIGHT, 'hovermode': 'closest' } def apply_dark_theme(fig: go.Figure, **kwargs) -> go.Figure: """ Apply dark theme to a Plotly figure. Args: fig: Plotly figure **kwargs: Additional layout parameters to override defaults Returns: Figure with dark theme applied """ layout_updates = DEFAULT_LAYOUT.copy() layout_updates.update(kwargs) fig.update_layout(**layout_updates) return fig def create_base_layout( title: str = "", xaxis_title: str = "", yaxis_title: str = "", **kwargs ) -> Dict: """ Create base layout configuration with dark theme. Args: title: Plot title xaxis_title: X-axis label yaxis_title: Y-axis label **kwargs: Additional layout parameters Returns: Layout dictionary """ layout = DEFAULT_LAYOUT.copy() layout.update({ 'title': title, 'xaxis': { **layout['xaxis'], 'title': xaxis_title }, 'yaxis': { **layout['yaxis'], 'title': yaxis_title } }) layout.update(kwargs) return layout def get_line_style(index: int) -> Dict: """ Get line style for multi-line plots. Args: index: Line index (0-based) Returns: Dictionary with color and dash pattern """ colors = [ DARK_THEME['primary'], DARK_THEME['secondary'], DARK_THEME['success'], DARK_THEME['warning'], DARK_THEME['danger'], '#00FFFF', # Cyan '#FF1493', # Deep Pink '#00FF00', # Lime '#FFA500', # Orange '#9370DB' # Medium Purple ] dash_patterns = ['solid', 'dash', 'dot', 'dashdot'] return { 'color': colors[index % len(colors)], 'dash': dash_patterns[(index // len(colors)) % len(dash_patterns)], 'width': 2 } def format_hover_template( x_label: str = "X", y_label: str = "Y", extra_fields: Dict[str, str] = None ) -> str: """ Create hover template for consistent hover display. Args: x_label: Label for x-axis value y_label: Label for y-axis value extra_fields: Additional fields to display Returns: Hover template string """ template = f"{x_label}: %{{x}}
" template += f"{y_label}: %{{y}}
" if extra_fields: for label, value_key in extra_fields.items(): template += f"{label}: {value_key}
" template += "" # Remove trace name return template # 3D scene configuration SCENE_3D_CONFIG = { 'bgcolor': DARK_THEME['plot_bg'], 'xaxis': { 'backgroundcolor': DARK_THEME['plot_bg'], 'gridcolor': DARK_THEME['grid'], 'showbackground': True, 'zerolinecolor': DARK_THEME['grid'], 'color': DARK_THEME['text'] }, 'yaxis': { 'backgroundcolor': DARK_THEME['plot_bg'], 'gridcolor': DARK_THEME['grid'], 'showbackground': True, 'zerolinecolor': DARK_THEME['grid'], 'color': DARK_THEME['text'] }, 'zaxis': { 'backgroundcolor': DARK_THEME['plot_bg'], 'gridcolor': DARK_THEME['grid'], 'showbackground': True, 'zerolinecolor': DARK_THEME['grid'], 'color': DARK_THEME['text'] } } def create_3d_scene_config( xaxis_title: str = "X", yaxis_title: str = "Y", zaxis_title: str = "Z", **kwargs ) -> Dict: """ Create 3D scene configuration. Args: xaxis_title: X-axis label yaxis_title: Y-axis label zaxis_title: Z-axis label **kwargs: Additional scene parameters Returns: Scene configuration dictionary """ scene = SCENE_3D_CONFIG.copy() scene['xaxis']['title'] = xaxis_title scene['yaxis']['title'] = yaxis_title scene['zaxis']['title'] = zaxis_title scene.update(kwargs) return scene # Export all theme components __all__ = [ 'DARK_THEME', 'COLORSCALES', 'DEFAULT_LAYOUT', 'SCENE_3D_CONFIG', 'apply_dark_theme', 'create_base_layout', 'get_line_style', 'format_hover_template', 'create_3d_scene_config' ] if __name__ == "__main__": # Test theme configuration import plotly.graph_objects as go import numpy as np print("Testing theme configuration...") # Create sample plot x = np.linspace(0, 10, 100) y1 = np.sin(x) y2 = np.cos(x) fig = go.Figure() # Add traces with themed styles style1 = get_line_style(0) style2 = get_line_style(1) fig.add_trace(go.Scatter( x=x, y=y1, name='Sin', line=style1, hovertemplate=format_hover_template("X", "Sin(X)") )) fig.add_trace(go.Scatter( x=x, y=y2, name='Cos', line=style2, hovertemplate=format_hover_template("X", "Cos(X)") )) # Apply dark theme layout = create_base_layout( title="Theme Test Plot", xaxis_title="X Value", yaxis_title="Y Value" ) fig.update_layout(**layout) # Save test fig.write_html("test_theme.html") print("✅ Theme test saved to test_theme.html")