Terminal 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)

DisplaySettings(*args, **kwargs)

Persistent configuration for terminal display formatting.

Displayable()

Abstract base for objects with Rich terminal display.

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#

class jangada.display.DisplaySettings(*args, **kwargs)#

Bases: Persistable

Persistent configuration for terminal display formatting.

DisplaySettings provides centralized control over all visual aspects of terminal output, including console width, panel styling, and table formatting. Settings can be customized per-instance and saved as reusable themes.

All styling properties use Rich’s style syntax, supporting colors, attributes (bold, italic), and combinations.

Attributes:
console_widthint

Maximum console output width in characters. Default 150.

property_stylestr

Style for property labels in forms. Default ‘bold bright_yellow’.

panel_border_stylestr

Style for panel borders. Default ‘bright_cyan’.

panel_boxstr

Box style name from rich.box. Default ‘ROUNDED’.

panel_title_alignstr

Panel title alignment. Default ‘center’.

table_index_stylestr or None

Style for table index column. Default None.

table_header_stylestr or None

Style for table headers. Default ‘bold bright_yellow’.

table_round_floatsint or None

Decimal places for float rounding. Default None.

table_spacingint

Column spacing in characters. Default 4.

See also

Displayable

Uses DisplaySettings for formatting

Notes

As a Persistable subclass, DisplaySettings can be saved to HDF5 files with the .disp extension, making themes portable and shareable.

Rich style strings support: - Colors: ‘red’, ‘blue’, ‘bright_yellow’, ‘#FF0000’ - Modifiers: ‘bold’, ‘italic’, ‘underline’, ‘dim’ - Combinations: ‘bold red’, ‘italic bright_cyan’

Examples

Create and customize settings:

settings = DisplaySettings()
settings.panel_border_style = 'green'
settings.console_width = 120
settings.table_spacing = 8

Save as theme:

settings.save('my_theme.disp')

Load theme:

settings = DisplaySettings.load('my_theme.disp')
obj.display_settings = settings

Displayable#

class jangada.display.Displayable#

Bases: ABC

Abstract base for objects with Rich terminal display.

Displayable provides a framework for beautiful terminal output via Rich panels. Subclasses define content through abstract methods _title() and _content(), while the framework handles formatting, styling, and rendering.

Integrates with Rich’s protocol (__rich__) and provides string output (__str__), making objects displayable in both Rich-aware and standard contexts.

Attributes:
display_settingsDisplaySettings

Configuration for display formatting. Auto-created per instance via factory, allowing independent customization.

See also

DisplaySettings

Configuration class

format_as_form

Helper for key-value displays

format_as_table

Helper for DataFrame displays

Notes

Uses template method pattern: framework provides structure, subclasses provide content. This ensures consistent styling across all displays.

Examples

Simple implementation:

class Sensor(Displayable):
    def __init__(self, name, temp):
        self.name = name
        self.temp = temp

    def _title(self):
        return Text(f"Sensor: {self.name}")

    def _content(self):
        return f"Temperature: {self.temp}°C"

With table:

class Report(Displayable):
    def __init__(self, df):
        self.df = df

    def _title(self):
        return Text("Data Report")

    def _content(self):
        return self.format_as_table(self.df)

Custom styling:

obj = Sensor("Temp-01", 23.5)
obj.display_settings.panel_border_style = 'green'
print(obj)
display_settings: DisplaySettings#

Configuration for display formatting and styling.

Provides access to all visual customization options for this object’s terminal display. Each instance receives its own DisplaySettings object by default (via factory), allowing per-object customization without affecting other instances.

See also

DisplaySettings

The settings class with all options

format_as_form

Uses property_style setting

format_as_table

Uses table_* settings

Notes

Factory Behavior

The display_settings property uses a factory function to create a new DisplaySettings instance for each Displayable object. This ensures: - Each instance has independent settings by default - No unexpected sharing between objects - Settings can be customized per-object without side effects

Sharing Settings

To share settings across multiple objects, explicitly assign the same DisplaySettings instance:

shared_settings = DisplaySettings() obj1.display_settings = shared_settings obj2.display_settings = shared_settings

Now changes to shared_settings affect both objects.

Persistence

DisplaySettings is Persistable, so settings can be: - Saved to .disp files (themes) - Loaded from saved themes - Versioned and shared across projects - Used to maintain consistent styling

Immediate Effect

Changes to display_settings take effect immediately on the next render: - print(obj) uses current settings - obj.__rich__() uses current settings - obj.to_html() uses current settings

No need to “apply” or “refresh” - just change and use.

Available Settings

See DisplaySettings documentation for all available options: - Console: console_width - Panel: panel_border_style, panel_box, panel_title_align - Property labels: property_style - Tables: table_index_style, table_header_style, table_round_floats, table_spacing

Examples

Customize styling:

obj.display_settings.panel_border_style = 'green'
obj.display_settings.table_header_style = 'bold magenta'
obj.display_settings.console_width = 120

Access specific settings:

width = obj.display_settings.console_width
border = obj.display_settings.panel_border_style
spacing = obj.display_settings.table_spacing

Share settings across objects:

shared = DisplaySettings()
shared.panel_border_style = 'yellow'
shared.table_spacing = 8

obj1.display_settings = shared
obj2.display_settings = shared
# Both objects now share the same styling

Load saved theme:

theme = DisplaySettings.load('dark_theme.disp')
obj.display_settings = theme

Save current settings:

obj.display_settings.save('my_custom_theme.disp')

Temporary style changes:

# Modify for one display
obj.display_settings.console_width = 100
print(obj)

# Reset to defaults
obj.display_settings = DisplaySettings()
format_as_form(data: dict[str, str] | Series) Table#

Format data as key-value form.

Creates two-column table with keys (left) and values (right).

Parameters:
datadict[str, str] or pandas.Series

Key-value pairs to display.

Returns:
Table

Rich Table in form layout.

Notes

Keys automatically get ‘:’ appended and use property_style. Values are left-aligned with no special styling.

Examples

>>> form = self.format_as_form({
...     'Name': 'Alice',
...     'Age': '25',
...     'City': 'NYC'
... })
format_as_table(frame: DataFrame, show_index: bool = True, format_index_as_property: bool = False, format_header_as_property: bool = False, align_header: str | dict[str, str] = 'center', align_column: str | dict[str, str] | None = None, max_rows: int = 31, **kwargs) Table#

Format DataFrame as Rich table.

Creates formatted table with automatic alignment, optional truncation, and extensive customization options.

Parameters:
framepandas.DataFrame

Data to display.

show_indexbool, optional

Include index column. Default True.

format_index_as_propertybool, optional

Use property_style for index. Default False.

format_header_as_propertybool, optional

Use property_style for headers. Default False.

align_headerstr or dict[str, str], optional

Header alignment. Default ‘center’.

align_columnstr, dict[str, str], or None, optional

Column alignment. None = auto-detect. Default None.

max_rowsint, optional

Max rows before truncation. Default 31.

**kwargs

Additional options: - header_style : str or None - index_style : str or None - round_floats : int, dict, or None

Returns:
Table

Formatted Rich Table.

Raises:
TypeError

If alignment or rounding parameters have invalid types.

Notes

Auto-alignment (align_column=None): numeric right, datetime/text left.

Truncation: Shows first n/2 rows, ‘…’, last n/2 rows when > max_rows.

Creates DataFrame copy for processing.

Examples

Basic usage:

>>> table = self.format_as_table(df)

Custom alignment:

>>> table = self.format_as_table(df, align_column='right')

Float rounding:

>>> table = self.format_as_table(df, round_floats=2)

Large dataset:

>>> table = self.format_as_table(df, max_rows=20)
to_html(width: int | None = None, **kwargs) str#

Export display as HTML.

Returns:
str

HTML with inline styles.

Examples

>>> html = obj.to_html()
>>> with open('output.html', 'w') as f:
...     f.write(html)
to_svg(width: int | None = None, **kwargs) str#

Export display as SVG.

Returns:
str

SVG vector graphics.

Examples

>>> svg = obj.to_svg()
>>> with open('output.svg', 'w') as f:
...     f.write(svg)

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_widthint

Maximum console width in characters. Default: 150

Example:

settings.console_width = 120  # Narrower display
settings.console_width = 200  # Wider display

Property Styling#

property_stylestr

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_stylestr

Border color/style. Default: ‘bright_cyan’

Example:

settings.panel_border_style = 'green'
settings.panel_border_style = 'bold red'
settings.panel_border_style = '#FF00FF'
panel_boxstr

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_alignstr

Title alignment. Default: ‘center’

Valid values: ‘left’, ‘center’, ‘right’

Example:

settings.panel_title_align = 'left'

Table Styling#

table_index_stylestr or None

Index column style. Default: None

Example:

settings.table_index_style = 'dim'
settings.table_index_style = 'bold yellow'
table_header_stylestr 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_floatsint or None

Global float rounding. Default: None

Example:

settings.table_round_floats = 2  # Two decimals
settings.table_round_floats = None  # Full precision
table_spacingint

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'''
<html>
<head><title>Report</title></head>
<body>
    <h1>System Report</h1>
    {obj.to_html()}
</body>
</html>
'''

SVG export:

svg = obj.to_svg()

with open('diagram.svg', 'w') as f:
    f.write(svg)

Features:

  • Preserves colors and formatting

  • Self-contained (inline styles)

  • HTML includes CSS

  • SVG is vector graphics (scalable)

Custom Panel Configuration#

Override _display_panel() for complete control:

def _display_panel(self):
    return Panel(
        self._content(),
        title=self._title(),
        subtitle="Footer text",  # Add footer
        border_style=self.display_settings.panel_border_style,
        padding=(1, 2),  # Custom padding
        expand=True,  # Fill width
    )

Rich Renderables#

Return any Rich renderable from _content():

from rich.tree import Tree
from rich.syntax import Syntax
from rich.table import Table

def _content(self):
    # Tree structure
    tree = Tree("Components")
    for comp in self.components:
        tree.add(comp.name)
    return tree

# Or syntax-highlighted code
def _content(self):
    return Syntax(self.code, "python")

# Or styled table
def _content(self):
    table = Table(show_header=True, header_style="bold")
    table.add_column("Name")
    table.add_column("Value")
    # ... add rows
    return table

Best Practices#

Styling Guidelines#

Use semantic colors:

  • Green: Success, active, OK

  • Red: Error, inactive, warning

  • Yellow: Attention, highlight

  • Cyan/Blue: Info, neutral

  • Dim: Secondary info

Example:

status = "[green]Active[/green]" if self.active else "[red]Inactive[/red]"
priority = "[yellow]High[/yellow]" if self.priority > 5 else "Normal"

Keep styles consistent:

  • Use display_settings for global styles

  • Override only when needed

  • Document color meanings

Content Organization#

Progressive disclosure:

  • Summary info first

  • Detailed data after

  • Most important at top

Example:

def _content(self):
    return Group(
        self._summary(),      # Key info
        "",
        self._details(),      # Full data
        "",
        self._footer()        # Meta info
    )

Appropriate detail level:

  • Forms: 5-15 items

  • Tables: 10-30 rows

  • Text: 5-10 lines

Use truncation:

  • max_rows for large datasets

  • Summaries for long text

  • Pagination if needed

Performance#

Avoid expensive operations in _content():

  • Cache computed values

  • Pre-process data

  • Don’t query databases directly

Example:

class DataView(Displayable):
    def __init__(self, data):
        self.data = data
        self._summary = self._compute_summary()  # Cache

    def _compute_summary(self):
        # Expensive computation done once
        return {...}

    def _content(self):
        # Use cached summary
        return self.format_as_form(self._summary)

Error Handling#

Handle missing data gracefully:

def _content(self):
    if self.data is None:
        return "[dim italic]No data available[/dim italic]"

    if len(self.data) == 0:
        return "[dim italic]No records found[/dim italic]"

    return self.format_as_table(self.data)

Validate in __init__, not _content:

def __init__(self, data):
    if data is None:
        raise ValueError("Data cannot be None")
    self.data = data

Troubleshooting#

Common Issues#

Panel not displaying:

  • Check that _title() and _content() return valid types

  • Ensure Rich is installed

  • Verify console width setting

Colors not showing:

  • Terminal may not support colors

  • Check force_terminal setting

  • Try different terminal emulator

Table formatting issues:

  • DataFrame has invalid dtypes

  • Missing required columns

  • Index issues (try show_index=False)

Float rounding not working:

  • Check dtype is actually float

  • Verify round_floats format (int or dict)

  • Column names must match exactly in dict

Theme Issues#

Theme not loading:

# Check file exists
from pathlib import Path
assert Path('theme.disp').exists()

# Load with error handling
try:
    settings = DisplaySettings.load('theme.disp')
except Exception as e:
    print(f"Failed to load theme: {e}")

Settings not applying:

# Verify settings object is assigned
assert obj.display_settings is not None

# Check specific setting
print(obj.display_settings.panel_border_style)

Export Issues#

HTML/SVG missing content:

  • Content may not render in export context

  • Some Rich features don’t export well

  • Try simpler renderables

File encoding issues:

# Use explicit UTF-8
html = obj.to_html()
with open('output.html', 'w', encoding='utf-8') as f:
    f.write(html)

Performance Considerations#

String Rendering#

__str__() captures Rich output to string:

  • Creates StringIO buffer

  • Renders to buffer

  • Returns buffered string

For many objects, consider:

  • Rendering once, caching result

  • Direct __rich__() in Rich contexts

  • Lazy rendering

Table Formatting#

format_as_table() creates DataFrame copy:

  • Memory allocation for copy

  • Type conversions (astype)

  • String formatting for display

For large DataFrames:

  • Use max_rows to limit size

  • Pre-filter data

  • Consider pagination

Display Settings#

Each instance has independent settings:

  • Small memory overhead

  • Factory creates new instance each time

  • Share settings to reduce overhead:

    shared = DisplaySettings()
    for obj in objects:
        obj.display_settings = shared
    

See Also#

External Resources#