Перейти к содержанию

Интерактивная визуализация графиков с помощью egui_graphs

Posted on:7 мая 2023 г. at 13:17

Example Dynamic OG Image link Недавно библиотека визуализации графиков egui_graphs, написанная на rust и работающая на базе egui для пользовательского интерфейса и petgraph в качестве серверной части graph, вышла в версии 0.3.0. Чтобы отпраздновать эту веху, я хотел бы продемонстрировать, насколько просто визуализировать граф и добавить интерактивности с помощью этого инструмента.

Шаг 1: Настройка структуры InteractiveApp.

Сначала давайте определим структуру InteractiveApp, которая будет содержать граф.

pub struct InteractiveApp {
    g: StableGraph<Node<()>, Edge<()>>,
}

Шаг 2: Реализация функции new().

Далее давайте реализуем функцию new() для структуры BasicApp.

impl InteractiveApp {
    fn new(_: &CreationContext<'_>) -> Self {
        let g = generate_graph();
        Self { g }
    }
}

Здесь мы вызываем функцию generate_graph(). Допустим, нам нужно 30 узлов и 60 ребер.

Шаг 3: Функция случайного графа.

Теперь мы создаем функцию generate_graph.

fn generate_graph() -> StableGraph<Node<()>, Edge<()>> {
    let mut g: StableGraph<Node<()>, Edge<()>> = StableGraph::new();

    let a = g.add_node(Node::new(egui::Vec2::new(0., SIDE_SIZE), ()));
    let b = g.add_node(Node::new(egui::Vec2::new(-SIDE_SIZE, 0.), ()));
    let c = g.add_node(Node::new(egui::Vec2::new(SIDE_SIZE, 0.), ()));

    g.add_edge(a, b, Edge::new(()));
    g.add_edge(b, c, Edge::new(()));
    g.add_edge(c, a, Edge::new(()));

    g
}

Шаг 4: Реализация функции update().

Теперь давайте реализуем функцию update() для нашего Interactiveapp. Эта функция создает виджет egui_graphs::GraphView, предоставляющий изменяемую ссылку на наш граф, и добавляет его в egui::CentralPanel, используя функцию ui.add() для добавления виджетов.

impl App for InteractiveApp {
    fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(
                &mut GraphView::new(&mut self.g).with_interactions(&SettingsInteraction {
                    node_drag: true,
                    node_click: true,
                    node_select: true,
                    node_multiselect: true,
                    ..Default::default()
                }),
            );
        });
    }
}

Здесь мы инициализируем GraphView с помощью функции конструктора with_interactions и передаем туда экземпляр SettingsInteraction со свойствами взаимодействия. По сути, это место, которое добавляет все взаимодействия в виджет.

Шаг 5: Запуск приложения.

Наконец, запустите приложение, используя функцию run_native() с указанными собственными параметрами и InteractiveApp.

fn main() {
    let native_options = eframe::NativeOptions::default();
    run_native(
        "egui_graphs_interactive_demo",
        native_options,
        Box::new(|cc| Box::new(InteractiveApp::new(cc))),
    )
    .unwrap();
}

Как только приложение будет запущено, мы увидим полностью интерактивный график с возможностью перетаскивания и выбора узлов.

Полный код для этого примера:

use eframe::{run_native, App, CreationContext};
use egui::Context;
use egui_graphs::{Edge, GraphView, Node, SettingsInteraction};
use petgraph::stable_graph::StableGraph;

const SIDE_SIZE: f32 = 50.;

pub struct InteractiveApp {
	g: StableGraph<Node<()>, Edge<()>>,
}

impl InteractiveApp {
	fn new(_: &CreationContext<'_>) -> Self {
		let g = generate_graph();
		Self { g }
	}
}

impl App for InteractiveApp {
	fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
		egui::CentralPanel::default().show(ctx, |ui| {
			ui.add(
				&mut GraphView::new(&mut self.g).with_interactions(&SettingsInteraction {
					node_drag: true,
					node_click: true,
					node_select: true,
					node_multiselect: true,
					..Default::default()
				}),
			);
		});
	}
}

fn generate_graph() -> StableGraph<Node<()>, Edge<()>> {
	let mut g: StableGraph<Node<()>, Edge<()>> = StableGraph::new();
	let a = g.add_node(Node::new(egui::Vec2::new(0., SIDE_SIZE), ()));
	let b = g.add_node(Node::new(egui::Vec2::new(-SIDE_SIZE, 0.), ()));
	let c = g.add_node(Node::new(egui::Vec2::new(SIDE_SIZE, 0.), ()));
	g.add_edge(a, b, Edge::new(()));
	g.add_edge(b, c, Edge::new(()));
	g.add_edge(c, a, Edge::new(()));
	g
}

fn main() {
	let native_options = eframe::NativeOptions::default();
	run_native(
		"egui_graphs_interactive_demo",
		native_options,
		Box::new(|cc| Box::new(InteractiveApp::new(cc))),
	)
	.unwrap();
}