Interactive graph visualization widget for rust powered by egui and petgraph
Interactive graph visualization widget for rust powered by egui and petgraph
Graph visualization with rust and egui.
The project provides a GraphView for the egui framework, enabling easy visualization of interactive graphs in rust. The goal is to implement the very basic engine for graph visualization within egui, which can be easily extended and customized for your needs.
Check the web-demo for the comprehensive overview of the widget possibilities.
The project is not in active development. Feel free to fork it and tweak for your needs.
The source code of the following steps can be found in the basic example.
BasicApp structFirst, let's define the BasicApp struct that will hold the graph.
pub struct BasicApp {
g: egui_graphs::Graph,
}
new() functionNext, implement the new() function for the BasicApp struct.
impl BasicApp {
fn new(_: &eframe::CreationContext) -> Self {
Self { g: generate_graph() }
}
}
Create a helper function called generate_graph(). In this example, we create three nodes and three edges.
fn generate_graph() -> egui_graphs::Graph {
let mut g = egui_graphs::Graph::new();
let a = g.add_node(());
let b = g.add_node(());
let c = g.add_node(());
g.add_edge(a, b, ());
g.add_edge(b, c, ());
g.add_edge(c, a, ());
g
}
eframe::App traitNow, lets implement the eframe::App trait for the BasicApp. In the ui() function, we create an egui::CentralPanel and show the egui_graphs::GraphView in it.
impl eframe::App for BasicApp {
fn ui(&mut self, ui: &mut egui::Ui, _: &mut eframe::Frame) {
egui::CentralPanel::default().show(ui, |ui| {
egui_graphs::DefaultGraphView::new().show(ui, &mut self.g);
});
}
}
Finally, run the application using the eframe::run_native() function.
fn main() {
eframe::run_native(
"egui_graphs_basic_demo",
eframe::NativeOptions::default(),
Box::new(|cc| Ok(Box::new(BasicApp::new(cc)))),
)
.unwrap();
}
You can further customize the appearance and behavior of your graph by modifying the settings or adding more nodes and edges as needed.
Custom DisplayNode and DisplayEdge implementations receive a DrawContext. Use
ctx.style.labels_always(), ctx.style.resolve_node_stroke(...), and
ctx.style.resolve_edge_stroke(...) inside those renderers to preserve the graph-wide label and
stroke settings. The public EdgeShapeBuilder, EdgeShapeProps, and TipProps types can be used
to construct the same straight, curved, looped, and arrow-tipped edge geometry as the default
renderer without copying its internals.
Built-in layouts with a pluggable API. The Layout trait powers layout selection and persistence; you can plug different algorithms or implement your own.
DefaultGraphView).// Default random layout
egui_graphs::DefaultGraphView::new().show(ui, &mut graph);
// Pick a specific layout (Hierarchical)
type L = egui_graphs::LayoutHierarchical;
type S = egui_graphs::LayoutStateHierarchical;
egui_graphs::GraphView::::new().show(ui, &mut graph);
// Force‑Directed (FR) with Center Gravity
type L = egui_graphs::LayoutForceDirected;
type S = egui_graphs::FruchtermanReingoldWithCenterGravityState;
egui_graphs::GraphView::::new().show(ui, &mut graph);
A naive O(n²) force-directed layout (Fruchterman–Reingold style) is included. It exposes adjustable simulation parameters (step size, damping, etc.). See the demo for a live tuning panel. Built-in options include the baseline Fruchterman–Reingold and an extended variant with composable “extras” (e.g., Center Gravity).
Select algorithm via the layout type parameter (public aliases):
use egui_graphs::{LayoutForceDirected, FruchtermanReingold, FruchtermanReingoldState};
type L = LayoutForceDirected;
type S = FruchtermanReingoldState;
egui_graphs::GraphView::::new().show(ui, &mut graph);
Use FruchtermanReingoldWithExtras to apply base FR forces plus your extras each frame. Built-in extra: Center Gravity.
use egui_graphs::{
LayoutForceDirected,
FruchtermanReingoldWithCenterGravity,
FruchtermanReingoldWithCenterGravityState,
};
type L = LayoutForceDirected;
type S = FruchtermanReingoldWithCenterGravityState;
let mut state = egui_graphs::get_layout_state::(ui, None);
state.base.is_running = true;
state.extras.0.params.c = 0.2;
egui_graphs::set_layout_state(ui, state, None);
egui_graphs::GraphView::::new().show(ui, &mut graph);
Implement ExtraForce and compose it through Extra in a tuple. Extra-force parameters are persisted with layout state, so they must implement Serialize and Deserialize.
The complete runnable version is in the custom_force example.
…
Composition is order-sensitive; each enabled extra accumulates into the shared displacement vector in tuple order.
GraphView::show returns a GraphViewResponse. Its response field is the standard egui::Response for the allocated graph area, while changes contains the graph-specific changes produced by that call:
let result = egui_graphs::DefaultGraphView::new().show(ui, &mut graph);
if result.response.hovered() {
// The pointer is over this GraphView.
}
for change in result.changes {
println!("{change:?}");
}
Changes are transient and ordered by occurrence. Repeated changes are kept as separate entries, and an interaction-free frame returns an empty vector. Variants involving nodes or edges use the graph's NodeIndex or EdgeIndex type; graph-wide changes such as pan and zoom do not depend on an entity index. Store or aggregate a batch in your application when it must outlive the current frame. See the graph_view_response example for a complete application.
Crates:
Build from the workspace root:
cargo build --workspace
Prerequisites:
rustup target add wasm32-unknown-unknown
cargo install trunk # if not installed
Serve locally:
cd crates/demo-web
trunk serve
# opens http://127.0.0.1:8080 (or similar)
Build static assets:
cd crates/demo-web
trunk build
# output in crates/demo-web/dist
From the workspace root, specify the package and the example name:
# demo example
cargo r --release --example demo
# another example (basic)
cargo r --release --example basic
# custom force-directed layout
cargo r --release --example custom_force
# inspect per-frame GraphView responses
cargo r --release --example graph_view_response
No open issues yet, or sync has not completed.