rataflow README

The project's README from github.com/furkankly/rataflow. As one markdown file: README.md.

rataflow

Interactive node-based UIs for the terminal.

crates.io docs.rs downloads build status minimum supported Rust version

rataflow.furkankly.dev · every example in your browser, the crate itself compiled to WASM

A terminal canvas with two nodes wired by a step edge: a shell running tail -f, and a live request-rate sparkline

rataflow is a library for building node-based UIs in the terminal, from a static diagram to a fully interactive editor. Built on ratatui, inspired by xyflow (React Flow).

rataflow demo

Installation

cargo add rataflow

Or add to your Cargo.toml:

[dependencies]
rataflow = "0.1"

Features

Graph model

Interaction

Rendering

Layout

Integration

Concepts

Quick Start

A list of edges is enough to get a graph on screen. Nodes come from the unique names, positions from the layout, handles from its direction. It's draggable, pannable and zoomable from the first frame.

use rataflow::{Flow, Sugiyama};

let mut flow: Flow = Flow::from_edges(
    &[("Start", "Process"), ("Process", "End")],
    Sugiyama::vertical(),
)?;

To say more than that, build the graph yourself. The defaults come apart into their pieces: your own positions, handles, content types and edge kinds.

use rataflow::{Flow, Node, Edge, StepEdge};

// Create nodes with auto-sized text content
let nodes = vec![
    Node::from_text("a", (10.0, 10.0), "Node A"),
    Node::from_text("b", (40.0, 10.0), "Node B"),
];

// Create edges
let edges: Vec<Edge<StepEdge>> = vec![
    Edge::new("e1", "a", "b"),
];

// Create flow (`?` here assumes an enclosing `fn main() -> Result<..>`)
let mut flow = Flow::with_graph(nodes, edges)?;

// Request fit-view (applied at render time)
flow.request_fit_view();

// Render in your draw loop
terminal.draw(|f| {
    f.render_widget(&mut flow, f.area());
})?;

The examples/ directory has a runnable demo for every feature. A few starting points:

Run any of them with cargo run --example <name>.

Event Handling

Event handlers return an EventResponse: NotHandled, Handled, or Event(Vec<FlowEvent>). A single interaction can produce several events, for example NodeClicked followed by SelectionChanged:

use rataflow::FlowEvent;

for event in flow.handle_mouse_event(mouse.into()).into_events() {
    match event {
        FlowEvent::NodeClicked { node_id } => {
            // Show details, fetch data, etc.
        }
        FlowEvent::ConnectionCompleted(conn) => {
            // Add the edge, then persist to backend, validate, etc.
            flow.add_edge_from_connection(conn, StepEdge::default());
        }
        FlowEvent::SelectionChanged { node_ids, .. } => {
            // Update sidebar with current selection
        }
        _ => {}
    }
}

Under the Hood

A terminal cell grid doesn't give you what a browser does. There's no compositor, no stacking contexts, and no coordinates for anything drawn past the screen edge. A few of the pieces this library fills in:

See docs/ARCHITECTURE.md for the design rationale, and docs/INTERNALS.md for how it is implemented.

I've written this up as a series, Node-based UIs in the terminal. The first post covers the whole surface, and the other four each go one level down:

Event Loop

One operational gotcha: terminal backends deliver every raw mouse event individually (125-1000Hz), unlike browsers, which coalesce mouse moves between frames. During a drag the unprocessed events queue up and the input visibly lags.

Drain all pending events before each render:

'main: loop {
    terminal.draw(|f| {
        f.render_widget(&mut flow, area);
    })?;

    // Wait up to 16ms (~60 FPS) for the first event, then drain the rest
    if event::poll(Duration::from_millis(16))? {
        loop {
            match event::read()? {
                Event::Key(key) => {
                    if key.code == KeyCode::Char('q') { break 'main; }
                    flow.handle_key_event(key.into());
                }
                Event::Mouse(mouse) => {
                    for event in flow.handle_mouse_event(mouse.into()).into_events() {
                        match event {
                            FlowEvent::NodeClicked { node_id } => { /* ... */ }
                            _ => {}
                        }
                    }
                }
                _ => {}
            }
            if !event::poll(Duration::ZERO)? { break; }
        }
    }
}

All examples use this pattern. See examples/basic_async.rs for the tokio equivalent.

Performance

Benchmarks measure node dragging, the hardest sustained operation and the one where frame time turns into visible jank. Each test runs 20 consecutive move-and-render frames. Selection and mounting get no benchmarks of their own, because they are single-frame operations and dragging already covers the sustained case.

The graph topology and size (25x25 chain = 625 nodes, 624 edges) match xyflow's stress test. Frame durations measured via performance.now() (WASM/xyflow) and std::time::Instant (native). Only the 20 mousemove frames are reported.

cargo run --release --example stress_test -- --bench        # Headless benchmark (25x25 default)
cargo run --release --example stress_test                   # Interactive (t=drag, a=all, q=quit)

Native

Headless benchmark, 200x60 terminal buffer (a typical fullscreen terminal at 1080p, fixed so numbers compare across machines). Release build, chain topology.

Nodes Edges Drag Avg FPS
625 624 ~1.0ms ~1,000
10,000 9,999 ~6.6ms ~152
22,500 22,499 ~11.4ms ~88
40,000 39,999 ~18.1ms ~55

Grid topology (2 edges per node) roughly doubles render time: 37,500 nodes with 74,600 edges averages ~33ms.

Native vs WASM

At 625 nodes: ~1.0ms vs ~8ms. The ~8x overhead comes from the WebGL2 rendering pipeline and browser frame scheduling.

Full WASM scaling data
Nodes Edges Drag Avg Range
625 624 ~8ms 7-10ms
2,500 2,499 ~8ms 8-9ms
5,625 5,624 ~8ms 7-10ms
10,000 9,999 ~8ms 7-12ms
22,500 22,499 ~13ms 12-15ms
27,889 27,888 ~17ms 16-19ms

WASM vs xyflow (React Flow)

rataflow renders to a flat cell buffer on a WebGL2 canvas via ratzilla; xyflow renders to the DOM using React/Svelte. These are fundamentally different rendering architectures, so this isn't a "which is better". It's a concrete illustration of the tradeoffs each approach makes.

625 nodes, 624 edges. Same browser, same window.

Library Avg Frame Range Frames
rataflow WASM ~8ms 7-10ms 20/20
xyflow (React Flow) ~11ms 5-30ms 11-14/20

Scaling. How many nodes at equivalent frame time:

Library Nodes Edges Avg Frame Range
xyflow (React Flow) 625 624 ~11ms 5-30ms
rataflow WASM 10,000 9,999 ~8ms 7-12ms

16:1. rataflow WASM handles 10,000 nodes at the frame time xyflow needs for 625.

Feature Flags

Contributing

Pull requests are welcome.

License

MIT License

Acknowledgements

rataflow (furkankly/rataflow)

Searching Database…

Что это?rataflow — библиотека на Rust для создания нодовых интерактивных редакторов диаграмм прямо внутри терминала: от статичной диаграммы до полностью интерактивного нодового редактора.
Это отдельное приложение или библиотека, которую я встраиваю в свой Rust-проект?Это библиотека поверх терминального UI-фреймворка ratatui, созданная по образцу xyflow (React Flow) для веба: вы встраиваете её виджет Flow в собственное TUI-приложение, а не запускаете как программу.
Как добавить rataflow в проект?Добавьте его как зависимость в Cargo.toml, затем постройте граф из нод и рёбер. В каталоге examples/ есть запускаемая демка для каждой функции — запускаются командой cargo run --example.
Какие терминальные бэкенды он поддерживает?Он не привязан к бэкенду: поддерживаются crossterm (по умолчанию), termion и termwiz для нативных терминалов, плюс WebAssembly через ratzilla для запуска в браузере.
Есть ли подводные камни при настройке цикла событий?Терминальные бэкенды доставляют каждое сырое событие мыши по отдельности на частоте 125-1000 Гц, а не объединяют перемещения, как делают браузеры, поэтому при перетаскивании нужно выгребать все накопившиеся события перед каждым рендером, иначе ввод заметно запаздывает.
Как производительность rataflow соотносится с xyflow (React Flow)?В WASM-бенчмарках rataflow рисует 625 нод примерно за 8 мс против ~11 мс у xyflow, а примерно 10 000 нод он рендерит за то же время кадра, которое xyflow тратит всего на 625 нод.
Какие вспомогательные виджеты идут в комплекте?Вместе с Flow поставляются виджеты Controls, MiniMap и Background, обёрнутые вокруг экземпляра Flow, плюс встроенные типы нод и рёбер вроде TextContent, StepEdge, StraightEdge и FloatingEdge.
Можно ли сохранять и восстанавливать граф или сделать undo/redo?Да, снимки графа можно сериализовать через serde — это показывают примеры saverestore и undoredo.
Включена ли автоматическая раскладка графа?Да, встроена слоистая раскладка на основе алгоритма Сугиямы с настраиваемыми направлением, интервалами и отступами, либо можно подавать позиции из собственного внешнего алгоритма раскладки.
What are similar projects?zoetrope, LogicFlow, neomacs, gitlogue, FTXUI, ratatui, sofka, abtop
Creator
furkankly
Size
4.2 MB
Stars
187
Age
2mo
Commits · 90 Days