##################### Terminal Display ##################### .. currentmodule:: jangada.display The display module provides a framework for creating beautiful, styled terminal output using the Rich library. Objects can define their visual representation through customizable panels, tables, and forms with persistent theme support. Overview ======== The display module provides two main classes: - **DisplaySettings** - Persistent configuration for all display styling - **Displayable** - Abstract base class for objects with Rich terminal display Key features: - Rich library integration for beautiful terminal output - Customizable themes (colors, styles, layouts) - Automatic table formatting with smart alignment - Form-style key-value displays - HTML/SVG export capabilities - Persistent settings (save/load themes) .. autosummary:: :toctree: generated/ DisplaySettings Displayable Quick Start =========== Basic displayable object:: from jangada.display import Displayable from rich.text import Text class Sensor(Displayable): def __init__(self, name, temperature): self.name = name self.temperature = temperature def _title(self): return Text(f"Sensor: {self.name}", style="bold") def _content(self): return f"Temperature: {self.temperature}°C" sensor = Sensor("Temp-01", 23.5) print(sensor) # Displays formatted panel Table display:: import pandas as pd from jangada.display import Displayable from rich.text import Text class DataView(Displayable): def __init__(self, df): self.df = df def _title(self): return Text("Data Table") def _content(self): return self.format_as_table(self.df, max_rows=20) df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}) view = DataView(df) print(view) Custom styling:: sensor = Sensor("Temp-01", 23.5) sensor.display_settings.panel_border_style = 'green' sensor.display_settings.console_width = 120 print(sensor) Save theme:: settings = DisplaySettings() settings.panel_border_style = 'magenta' settings.table_header_style = 'bold white' settings.save('my_theme.disp') # Load later sensor.display_settings = DisplaySettings.load('my_theme.disp') Class Reference =============== DisplaySettings --------------- .. autoclass:: DisplaySettings :members: :show-inheritance: Displayable ----------- .. autoclass:: Displayable :members: :inherited-members: :show-inheritance: Core Concepts ============= Template Method Pattern ----------------------- Displayable uses the **template method pattern**: subclasses implement content, framework handles formatting. **Subclass implements:** - ``_title()`` - Returns the panel title - ``_content()`` - Returns the panel body **Framework provides:** - Panel creation and styling - Border and title formatting - Console rendering - String capture - Export functions Example:: class MyObject(Displayable): # You implement these: def _title(self): return Text("My Object") def _content(self): return "Object contents here" # Framework does the rest: obj = MyObject() print(obj) # Formatted panel automatically Rich Integration ---------------- Displayable integrates deeply with Rich: **Magic methods:** - ``__rich__()`` - Direct Rich rendering - ``__str__()`` - Captured Rich output with ANSI codes **Usage contexts:** - Rich Console: ``console.print(obj)`` - IPython/Jupyter: Display ``obj`` directly - Standard print: ``print(obj)`` - Logging: ``logger.info(str(obj))`` Example:: from rich.console import Console console = Console() # All of these work: console.print(obj) # Uses __rich__() print(obj) # Uses __str__() obj # IPython display via __rich__() Display Settings ---------------- Each Displayable instance has its own **DisplaySettings** object: **Factory pattern:** - Each instance gets independent settings - No shared state by default - Can share settings explicitly if desired Example:: obj1 = MyObject() obj2 = MyObject() # Independent settings obj1.display_settings.panel_border_style = 'red' obj2.display_settings.panel_border_style = 'blue' # Or share settings shared = DisplaySettings() obj1.display_settings = shared obj2.display_settings = shared Helper Methods ============== format_as_form -------------- Creates key-value form displays: **Basic usage**:: def _content(self): return self.format_as_form({ 'Name': 'Alice', 'Age': '25', 'City': 'NYC' }) **With pandas Series**:: import pandas as pd def _content(self): info = pd.Series({ 'Mean': self.data.mean(), 'Std': self.data.std() }) return self.format_as_form(info) **Features:** - Keys automatically get ``:`` appended - Keys styled with ``property_style`` setting - Values left-aligned - Compact layout (no borders) format_as_table --------------- Creates formatted tables from DataFrames: **Basic usage**:: def _content(self): return self.format_as_table(self.df) **With options**:: def _content(self): return self.format_as_table( self.df, show_index=True, format_header_as_property=True, max_rows=20, round_floats=2 ) **Features:** - Auto-alignment by dtype (numbers right, text left) - Optional index column display - Truncation for large datasets (first n/2, ..., last n/2) - Float rounding (global or per-column) - Flexible column/header alignment - Rich styling integration **Alignment options**:: # All columns same alignment table = self.format_as_table(df, align_column='right') # Per-column alignment table = self.format_as_table(df, align_column={ 'name': 'left', 'value': 'right' }) # Auto-detect (default) table = self.format_as_table(df, align_column=None) **Float rounding**:: # Round all floats to 2 decimals table = self.format_as_table(df, round_floats=2) # Per-column rounding table = self.format_as_table(df, round_floats={ 'price': 2, 'percentage': 1 }) **Truncation**:: # Show first 10, ..., last 10 rows table = self.format_as_table(df, max_rows=21) DisplaySettings Configuration ============================== Console Settings ---------------- **console_width** : int Maximum console width in characters. Default: 150 Example:: settings.console_width = 120 # Narrower display settings.console_width = 200 # Wider display Property Styling ---------------- **property_style** : str Rich style for property labels in forms. Default: 'bold bright_yellow' Example:: settings.property_style = 'bold cyan' settings.property_style = 'italic bright_white' Panel Styling ------------- **panel_border_style** : str Border color/style. Default: 'bright_cyan' Example:: settings.panel_border_style = 'green' settings.panel_border_style = 'bold red' settings.panel_border_style = '#FF00FF' **panel_box** : str Box style name from rich.box. Default: 'ROUNDED' Valid values: - 'ROUNDED': ╭─╮ (friendly, default) - 'SQUARE': ┌─┐ (classic) - 'DOUBLE': ╔═╗ (formal) - 'HEAVY': ┏━┓ (bold) - 'MINIMAL': ╶─╴ (subtle) - 'ASCII': +--+ (compatibility) Example:: settings.panel_box = 'DOUBLE' settings.panel_box = 'MINIMAL' **panel_title_align** : str Title alignment. Default: 'center' Valid values: 'left', 'center', 'right' Example:: settings.panel_title_align = 'left' Table Styling ------------- **table_index_style** : str or None Index column style. Default: None Example:: settings.table_index_style = 'dim' settings.table_index_style = 'bold yellow' **table_header_style** : str or None Header row style. Default: 'bold bright_yellow' Example:: settings.table_header_style = 'bold white' settings.table_header_style = None # No styling **table_round_floats** : int or None Global float rounding. Default: None Example:: settings.table_round_floats = 2 # Two decimals settings.table_round_floats = None # Full precision **table_spacing** : int Column spacing in characters. Default: 4 Example:: settings.table_spacing = 2 # Compact settings.table_spacing = 8 # Spacious Usage Patterns ============== Simple Text Display ------------------- Display simple text with styling:: class Message(Displayable): def __init__(self, title, text): self.title_text = title self.text = text def _title(self): return Text(self.title_text, style="bold cyan") def _content(self): return self.text msg = Message("Alert", "System update available") print(msg) Styled Title ------------ Use Rich Text for advanced styling:: from rich.text import Text def _title(self): return Text.assemble( ("Sensor ", "bold"), (self.name, "cyan"), (" [", "dim"), (self.id[:8], "yellow"), ("]", "dim") ) Dynamic Styling --------------- Change style based on state:: def _title(self): style = "green" if self.active else "red" return Text(self.name, style=f"bold {style}") def _content(self): status = "[green]Active[/green]" if self.active else "[red]Inactive[/red]" return f"Status: {status}" Combined Layouts ---------------- Combine forms and tables:: from rich.console import Group def _content(self): # Summary info info = self.format_as_form({ 'Total Samples': str(len(self.data)), 'Mean': f'{self.data.mean():.2f}', 'Std Dev': f'{self.data.std():.2f}' }) # Detailed table table = self.format_as_table( self.data.head(10), max_rows=10 ) # Combine with blank line separator return Group(info, "", table) Nested Panels ------------- Create nested panel structures:: from rich.panel import Panel def _content(self): # Inner panels info_panel = Panel( self.format_as_form(self.info), title="Info" ) data_panel = Panel( self.format_as_table(self.data), title="Data" ) # Group them return Group(info_panel, data_panel) Conditional Content ------------------- Show different content based on state:: def _content(self): if not self.has_data: return "[dim italic]No data available[/dim italic]" if self.summary_mode: return self.format_as_form(self.get_summary()) else: return self.format_as_table(self.data) Advanced Topics =============== Theme Management ---------------- **Create themes**:: # Dark theme dark = DisplaySettings() dark.panel_border_style = 'bright_cyan' dark.property_style = 'bold bright_yellow' dark.save('dark.disp') # Light theme light = DisplaySettings() light.panel_border_style = 'blue' light.property_style = 'bold black' light.save('light.disp') **Apply themes**:: # Load and apply theme = DisplaySettings.load('dark.disp') obj.display_settings = theme # Or set directly obj.display_settings = DisplaySettings.load('corporate.disp') **Share themes**:: theme = DisplaySettings.load('theme.disp') for obj in objects: obj.display_settings = theme Export Functions ---------------- **HTML export**:: html = obj.to_html() with open('report.html', 'w') as f: f.write(html) # Embed in larger document full_html = f'''