ascii-petgraph 0.2.0

ASCII visualization for petgraph directed graphs with force-directed layout
Documentation

ascii-petgraph

Crates.io Documentation License

ASCII visualization for petgraph directed graphs. Render your graphs in the terminal with force-directed layouts and Unicode box-drawing.

  ╭───────╮
  │ Start │
  ╰───────╯
      │   init
      └───────────┐
                  ↓
             ╭─────────╮
             │ Loading │────→╭───────╮
             ╰─────────╯     │ Ready │
                  ↑          ╰───────╯
                  │              │
                  │retry         │complete
                  ↓              ↓
              ╭───────╮      ╭──────╮
              │ Error │─────→│ Done │
              ╰───────╯      ╰──────╯

Features

  • Force-directed layout - Physics simulation with spring forces, repulsion, and gravity
  • Unicode box-drawing - Beautiful node boxes and edge routing with arrows
  • Configurable styles - Single, double, rounded, or ASCII borders
  • Ratatui integration - Drop-in widget for TUI applications
  • Mutable color API - Change node/edge colors after layout
  • Extension trait - Add .to_ascii() to any petgraph graph

Installation

Add to your Cargo.toml:

[dependencies]

ascii-petgraph = "0.1"

petgraph = "0.8"

ratatui = "0.29"  # Optional, for TUI rendering

Quick Start

Basic Usage

use petgraph::graph::DiGraph;
use ascii_petgraph::RenderedGraph;

fn main() {
    // Create a petgraph
    let mut graph = DiGraph::new();
    let a = graph.add_node("Start");
    let b = graph.add_node("Process");
    let c = graph.add_node("End");
    
    graph.add_edge(a, b, "begin");
    graph.add_edge(b, c, "finish");

    // Render it
    let mut rendered = RenderedGraph::from_graph(graph);
    rendered.run_simulation();

    // Print to terminal
    let grid = rendered.render_to_grid();
    for y in 0..grid.size().1 {
        for x in 0..grid.size().0 {
            if let Some(cell) = grid.get(x, y) {
                print!("{}", cell.char);
            }
        }
        println!();
    }
}

Extension Trait (Recommended)

Import AsciiGraphExt to add .to_ascii() method to any petgraph graph:

use petgraph::graph::DiGraph;
use ascii_petgraph::AsciiGraphExt;

let mut graph = DiGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
graph.add_edge(a, b, "edge");

// Extension method - works on DiGraph, StableGraph, DiGraphMap
let mut rendered = graph.to_ascii();
rendered.run_simulation();

Ratatui Widget

use ascii_petgraph::{RenderedGraph, AsciiGraphExt};
use ratatui::Frame;

fn render(frame: &mut Frame, rendered: &mut RenderedGraph<&str, &str>) {
    frame.render_widget(rendered.widget(), frame.area());
}

Configuration

Builder API

use ascii_petgraph::{RenderedGraph, BoxBorder};
use petgraph::graph::DiGraph;

let graph: DiGraph<&str, &str> = /* ... */;

let mut rendered = RenderedGraph::builder()
    .graph(graph)
    .border_style(BoxBorder::Rounded)  // Rounded corners
    .gravity(1.5)                       // Stronger downward force
    .spring_constant(0.1)               // Tighter clustering
    .repulsion_constant(8000.0)         // More node separation
    .build();

rendered.run_simulation();

Box Border Styles

Style Example Characters
BoxBorder::Single ┌─┐│└─┘ Default
BoxBorder::Double ╔═╗║╚═╝ Emphasized
BoxBorder::Rounded ╭─╮│╰─╯ Modern
BoxBorder::Ascii +-+|+-+ Compatible
use ascii_petgraph::BoxBorder;

rendered.set_border_style(BoxBorder::Double);

Colors

Change colors after the graph is created (colors don't affect layout):

use ratatui::style::Color;

// Individual nodes
rendered.set_node_border_color(node_id, Color::Green);
rendered.set_node_text_color(node_id, Color::White);

// Individual edges
rendered.set_edge_color(edge_id, Color::Yellow);
rendered.set_edge_text_color(edge_id, Color::Gray);

// Bulk operations
rendered.set_all_node_border_colors(Color::Blue);
rendered.set_all_edge_colors(Color::Cyan);

// Reset to defaults
rendered.reset_node_styles();
rendered.reset_edge_styles();

Scaling for Large Graphs

When graphs exceed terminal width, use scaling modes:

use ascii_petgraph::ScalingMode;

// Truncate long labels to 8 chars
rendered.set_scaling_mode(ScalingMode::Truncate(8));

// Use numeric IDs (0, 1, 2...) instead of labels
rendered.set_scaling_mode(ScalingMode::NumericIds);

// Auto-detect best mode for terminal width
rendered.auto_scale(80);  // 80-column terminal

Supported Graph Types

The AsciiGraphExt trait is implemented for:

Type Description
DiGraph<N, E> Standard directed graph
StableGraph<N, E> Directed graph with stable indices
DiGraphMap<N, E> Directed graph with hashable nodes

Both owned and referenced graphs are supported:

// Owned (graph is consumed)
let rendered = graph.to_ascii();

// Reference (graph can still be used)
let rendered = (&graph).to_ascii();

Physics Simulation

The layout uses force-directed simulation:

  • Spring forces: Edges pull connected nodes together
  • Repulsion forces: Nodes push each other apart
  • Gravity: Pulls nodes downward (root nodes float up, sinks settle down)
  • Damping: Velocities decay for stability

Manual Simulation Control

// Step-by-step simulation
while !rendered.is_stable() {
    rendered.tick();
    // Can render intermediate states here
}

// Or run to completion
rendered.run_simulation();

println!("Converged after {} iterations", rendered.iterations());

Physics Parameters

Parameter Default Effect
spring_constant 0.05 Higher = tighter edge clustering
spring_length 150.0 Ideal edge length in physics units
repulsion_constant 10000.0 Higher = more node separation
gravity 0.3 Downward force strength
damping 0.85 Velocity decay (0-1)
max_iterations 1000 Simulation limit

Examples

Run the examples:

# Basic terminal output

cargo run --example basic


# Interactive TUI with colors

cargo run --example tui

State Machine Example

use petgraph::graph::DiGraph;
use ascii_petgraph::{RenderedGraph, BoxBorder};
use ratatui::style::Color;

let mut graph = DiGraph::new();
let idle = graph.add_node("Idle");
let running = graph.add_node("Running");
let paused = graph.add_node("Paused");
let done = graph.add_node("Done");

graph.add_edge(idle, running, "start");
graph.add_edge(running, paused, "pause");
graph.add_edge(paused, running, "resume");
graph.add_edge(running, done, "complete");

let mut rendered = RenderedGraph::builder()
    .graph(graph)
    .border_style(BoxBorder::Rounded)
    .build();

rendered.run_simulation();

// Color by state
rendered.set_node_border_color(idle, Color::Gray);
rendered.set_node_border_color(running, Color::Green);
rendered.set_node_border_color(paused, Color::Yellow);
rendered.set_node_border_color(done, Color::Blue);

API Reference

Core Types

Type Description
RenderedGraph<N, E> Main type holding graph + layout + styles
RenderedGraphBuilder<N, E> Builder for configuration
BoxBorder Node border style enum
NodeStyle Per-node styling (border, colors)
EdgeStyle Per-edge styling (line color, text color)
ScalingMode Label handling for large graphs

Key Methods

impl RenderedGraph<N, E> {
    // Construction
    fn from_graph(graph: DiGraph<N, E>) -> Self;
    fn builder() -> RenderedGraphBuilder<N, E>;
    
    // Simulation
    fn tick(&mut self);
    fn run_simulation(&mut self);
    fn is_stable(&self) -> bool;
    fn iterations(&self) -> usize;
    
    // Styling
    fn set_node_border_color(&mut self, node: NodeIndex, color: Color);
    fn set_node_text_color(&mut self, node: NodeIndex, color: Color);
    fn set_edge_color(&mut self, edge: EdgeIndex, color: Color);
    fn set_border_style(&mut self, style: BoxBorder);
    fn set_scaling_mode(&mut self, mode: ScalingMode);
    
    // Rendering
    fn render_to_grid(&mut self) -> CharGrid;
    fn widget(&mut self) -> impl Widget;
}

Comparison with ascii-dag

Feature ascii-petgraph ascii-dag
Layout Algorithm Force-directed Sugiyama (layered)
Input petgraph types Custom DAG type
Best For General graphs DAGs / hierarchies
Animation Simulation steps Static
Dependencies petgraph, ratatui Zero

Use ascii-petgraph when you already have a petgraph graph and want physics-based layout. Use ascii-dag for zero-dependency DAG visualization with hierarchical layout.

License

MIT License - Copyright (c) 2026 Eleftherios Ioannidis, Microsoft Corporation

See LICENSE for details.