ascii-dag
Modular ASCII DAG (Directed Acyclic Graph) renderer and generic cycle detection library for error chains, build systems, and dependency visualization.
Perfect for:
- π Error diagnostic visualization (Rust errors, etc.)
- π§ Build dependency graphs
- π Task scheduling visualization
- π Generic cycle detection in any data structure
- π IoT/WASM error analysis (no_std compatible)
Features
- β Tiny: ~77KB WASM (minimal example with release optimizations), zero dependencies
- β Fast: O(log n) grouping with binary search, zero-copy rendering
- β no_std: Works in embedded/WASM environments
- β Modular: Use DAG rendering, cycle detection, or both independently
- β Generic: Cycle detection, topological sorting, and dependency analysis work on any data structure
- β Rich Analysis: Root finding, impact analysis, graph metrics
- β Safe: Cycle detection built-in
- β Beautiful: Clean ASCII art with Unicode box drawing
Quick Start
DAG Rendering
use DAG;
Output:
[Error1]
β
β
[Error2]
β
β
[Error3]
Generic Cycle Detection
Detect cycles in any data structure using higher-order functions:
use detect_cycle_fn;
// Example: Check for circular dependencies in a package manager
let get_deps = ;
let packages = ;
if let Some = detect_cycle_fn else
Usage
Builder API (Dynamic Construction)
use DAG;
let mut dag = DAGnew;
// Add nodes
dag.add_node;
dag.add_node;
dag.add_node;
// Add edges (dependencies)
dag.add_edge; // Parse -> Compile
dag.add_edge; // Compile -> Link
println!;
Batch Construction (Static, Fast)
let dag = DAGfrom_edges;
println!;
Output:
[A]
β ββββββββββ
β β
[B] [C]
βββββββββββ
β
[D]
Zero-Copy Rendering
let dag = DAGfrom_edges;
let mut buffer = Stringwith_capacity;
dag.render_to; // No allocation!
Cycle Detection
use DAG;
let mut dag = DAGnew;
dag.add_node;
dag.add_node;
dag.add_node;
dag.add_edge;
dag.add_edge;
dag.add_edge; // Cycle!
if dag.has_cycle
Generic Cycle Detection for Custom Types
Use the trait-based API for cleaner code:
use CycleDetectable;
// Now just call:
if registry.has_cycle
Root Finding & Impact Analysis
use find_roots_fn;
use compute_descendants_fn;
let get_deps = ;
let packages = ;
// Find packages with no dependencies (can build first)
let roots = find_roots_fn;
// roots = ["core"]
// What breaks if "core" changes?
let impacted = compute_descendants_fn;
// impacted = ["lib-a", "lib-b", "app"]
Graph Metrics
use GraphMetrics;
let metrics = compute;
println!;
println!;
println!;
println!;
println!;
Supported Patterns
Simple Chain
[A] -> [B] -> [C]
Diamond (Convergence)
[A]
/ \
[B] [C]
\ /
[D]
Variable-Length Paths
[Root]
βββ [Short] βββββββ
β β
βββ [Long1] β
β β
β β
[Long2] β
ββββββββββββββ
β
[End]
Multi-Convergence
[E1] [E2] [E3]
βββββββ΄βββββββ
β
[Final]
no_std Support
extern crate alloc;
use DAG;
// Works in embedded environments!
WASM Integration
use *;
use DAG;
API Reference
Core Modules
The library is organized into focused, independently-usable modules:
ascii_dag::graph - DAG Structure
use DAG; // or just `use ascii_dag::DAG;` for backward compat
ascii_dag::cycles::generic - Generic Cycle Detection
use ;
// Function-based API
ascii_dag::layout - Graph Layout
Sugiyama hierarchical layout algorithm for positioning nodes.
ascii_dag::render - ASCII Rendering
Vertical, horizontal, and cycle visualization modes.
Size Comparison
| Library | Compiled Size | Dependencies |
|---|---|---|
| ascii-dag | ~2-3KB | 0 |
| petgraph | ~200KB+ | Many |
Limitations & Design Choices (v0.1.x)
This is an initial 0.x release focused on simplicity and zero dependencies. Current limitations:
Rendering
- No cross-level edge routing: Long-distance edges are simplified (suitable for error chains, not general graphs)
- Unicode box characters required: Best viewed in terminals with Unicode support
- Small-to-medium graphs: Optimized for <1000 nodes (typical error chains, build graphs)
Auto-created Nodes
- Nodes referenced in edges are auto-created as placeholders (
β¨IDβ©format) - Calling
add_node()on a placeholder promotes it to a labeled node ([Label]format) - This enables flexible graph construction (add edges first, labels later)
Performance
- Optimized hot paths: O(1) HashMap lookups, cached widths, zero allocations in rendering
- Intended scale: Hundreds of nodes render in microseconds
- Not optimized for: Massive graphs (>10k nodes), real-time updates, interactive editing
API Stability
- 0.x series: Breaking changes possible between minor versions
- Focused scope: No plans to add general graph algorithms (use petgraph for that)
- Simple is better: Will resist feature creep to maintain zero dependencies
What This Crate Does Well
β
Error chain visualization (primary use case)
β
Build dependency graphs
β
Small task DAGs
β
no_std/WASM compatibility
β
Fast compilation, tiny binaries
What To Use Instead
- Large graphs with layout algorithms β graphviz, petgraph
- Interactive graph editing β egui-graphs, graph-viz
- Advanced graph algorithms β petgraph, pathfinding
Examples
Run examples:
Performance & Configuration
Optimizations
The library is optimized for both performance and bundle size:
- Cached Adjacency Lists: O(1) child/parent lookups instead of O(E) iteration
- Zero-Copy Rendering: Direct buffer writes without intermediate allocations
- Cached Node Widths: Pre-computed to avoid repeated string formatting
- HashMap Indexing: O(1) IDβindex lookups instead of O(N) scans
Feature Flags
Control bundle size by enabling only what you need:
[]
= { = "0.1", = false, = ["std"] }
Available features:
std(default): Standard library supportgeneric(default): Generic cycle detection, topological sort, impact analysis, and metricswarnings: Enable debug warnings for auto-created nodes
Bundle Size Impact:
- Core renderer only (
--no-default-features --features std): ~41KB WASM - With generic features (default): ~77KB WASM
Resource Limits
Tested configurations:
- β Up to 1,000 nodes with acceptable performance
- β Dense graphs (high edge count) handled efficiently via cached adjacency lists
- β οΈ Very large graphs (>10,000 nodes) may experience slower layout computation
Memory usage:
- Base overhead: ~100 bytes per node (cached data structures)
- Adjacency lists: ~16 bytes per edge (index storage)
- Rendering buffers: Pre-allocated based on graph size estimate
Performance characteristics:
- Node/edge insertion: O(1) amortized
- Cycle detection: O(V + E) with early termination
- Rendering: O(V log V + E) for layout, O(V) for output generation
Security considerations:
- No unsafe code
- Deterministic execution
- For untrusted input, consider limiting graph size to prevent resource exhaustion
- Maximum node ID is
usize::MAX(formatted as up to 20 digits)
Use Cases
- Error Diagnostics: Visualize error dependency chains with cycle prevention
- Build Systems: Show compilation dependencies and detect circular imports
- Task Scheduling: Display task ordering and validate DAG structure
- Data Pipelines: Illustrate data flow and check for feedback loops
- Package Managers: Detect circular dependencies in packages
- Generic Cycle Detection: Apply to any tree/graph structure via closures
- IoT: Lightweight error reporting
- WASM: Client-side error visualization
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Contributions welcome! This project aims to stay small and focused.
Why ascii-dag?
- Simple and focused: Does one thing well - ASCII DAG rendering
- Not petgraph: We don't need general graph algorithms, just visualization
- Not graphviz: No external dependencies, works everywhere
- Zero dependencies: Works in no_std, WASM, and embedded environments
Created by Ash