some experiments

main
Antonio De Lucreziis 8 months ago
parent 49caa60d21
commit 425fcd8544

3814
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -5,3 +5,6 @@ edition = "2021"
[dependencies] [dependencies]
argh = "0.1.12" argh = "0.1.12"
[workspace]
members = ["examples/*"]

@ -6,7 +6,7 @@
Display help message. Display help message.
- `cargo run -- show -i ./examples/example.gfa` - `cargo run -- show -i ./dataset/example.gfa`
Parses the GFA file and prints the graph and various information. Parses the GFA file and prints the graph and various information.

File diff suppressed because one or more lines are too long

@ -0,0 +1,15 @@
[package]
name = "configurable"
version = "0.1.0"
license = "MIT"
edition = "2021"
[dependencies]
egui = "0.27"
eframe = "0.27"
petgraph = "0.6"
egui_graphs = { version = "0.20.0", features = ["events"] }
serde_json = "1.0"
fdg-sim = "0.9"
rand = "0.8"
crossbeam = "0.8"

@ -0,0 +1,10 @@
# Configurable
Configurable example where you can toggle settings of the `GraphView` widget and see the result immediately.
It also contains controls to play with the graph.
This example also demonstrates the usage of force-directed layout implemented on client side.
## run
```bash
cargo run --release -p configurable
```

@ -0,0 +1,584 @@
use std::time::Instant;
use crossbeam::channel::{unbounded, Receiver, Sender};
use eframe::{run_native, App, CreationContext};
use egui::{CollapsingHeader, Context, Pos2, ScrollArea, Slider, Ui};
use egui_graphs::events::Event;
use egui_graphs::{to_graph, DefaultEdgeShape, DefaultNodeShape, Graph, GraphView};
use fdg_sim::glam::Vec3;
use fdg_sim::{ForceGraph, ForceGraphHelper, Simulation, SimulationParameters};
use petgraph::stable_graph::{DefaultIx, EdgeIndex, NodeIndex, StableGraph};
use petgraph::Directed;
use rand::Rng;
use settings::{SettingsGraph, SettingsInteraction, SettingsNavigation, SettingsStyle};
mod settings;
const SIMULATION_DT: f32 = 0.035;
const EVENTS_LIMIT: usize = 100;
pub struct ConfigurableApp {
g: Graph<(), (), Directed, DefaultIx>,
sim: Simulation<(), f32>,
settings_graph: SettingsGraph,
settings_interaction: SettingsInteraction,
settings_navigation: SettingsNavigation,
settings_style: SettingsStyle,
last_events: Vec<String>,
simulation_stopped: bool,
fps: f64,
last_update_time: Instant,
frames_last_time_span: usize,
event_publisher: Sender<Event>,
event_consumer: Receiver<Event>,
pan: Option<[f32; 2]>,
zoom: Option<f32>,
}
impl ConfigurableApp {
fn new(_: &CreationContext<'_>) -> Self {
let settings_graph = SettingsGraph::default();
let (g, sim) = generate(&settings_graph);
let (event_publisher, event_consumer) = unbounded();
Self {
g,
sim,
event_consumer,
event_publisher,
settings_graph,
settings_interaction: SettingsInteraction::default(),
settings_navigation: SettingsNavigation::default(),
settings_style: SettingsStyle::default(),
last_events: Vec::default(),
simulation_stopped: false,
fps: 0.,
last_update_time: Instant::now(),
frames_last_time_span: 0,
pan: Option::default(),
zoom: Option::default(),
}
}
fn update_simulation(&mut self) {
if self.simulation_stopped {
return;
}
// the following manipulations is a hack to avoid having looped edges in the simulation
// because they cause the simulation to blow up;
// this is the issue of the fdg_sim engine we use for the simulation
// https://github.com/grantshandy/fdg/issues/10
// * remove loop edges
// * update simulation
// * restore loop edges
// remove looped edges
let looped_nodes = {
let graph = self.sim.get_graph_mut();
let mut looped_nodes = vec![];
let mut looped_edges = vec![];
graph.edge_indices().for_each(|idx| {
let edge = graph.edge_endpoints(idx).unwrap();
let looped = edge.0 == edge.1;
if looped {
looped_nodes.push((edge.0, ()));
looped_edges.push(idx);
}
});
for idx in looped_edges {
graph.remove_edge(idx);
}
self.sim.update(SIMULATION_DT);
looped_nodes
};
// restore looped edges
let graph = self.sim.get_graph_mut();
for (idx, _) in looped_nodes.iter() {
graph.add_edge(*idx, *idx, 1.);
}
}
/// Syncs the graph with the simulation.
///
/// Changes location of nodes in `g` according to the locations in `sim`. If node from `g` is dragged its location is prioritized
/// over the location of the corresponding node from `sim` and this location is set to the node from the `sim`.
///
/// If node or edge is selected it is added to the corresponding selected field in `self`.
fn sync_graph_with_simulation(&mut self) {
let g_indices = self.g.g.node_indices().collect::<Vec<_>>();
for g_n_idx in &g_indices {
let g_n = self.g.g.node_weight_mut(*g_n_idx).unwrap();
let sim_n = self.sim.get_graph_mut().node_weight_mut(*g_n_idx).unwrap();
let loc = sim_n.location;
g_n.set_location(Pos2::new(loc.x, loc.y));
}
// reset the weights of the edges
self.sim.get_graph_mut().edge_weights_mut().for_each(|w| {
*w = 1.;
});
}
fn update_fps(&mut self) {
self.frames_last_time_span += 1;
let now = Instant::now();
let elapsed = now.duration_since(self.last_update_time);
if elapsed.as_secs() >= 1 {
self.last_update_time = now;
self.fps = self.frames_last_time_span as f64 / elapsed.as_secs_f64();
self.frames_last_time_span = 0;
}
}
fn reset_graph(&mut self, ui: &mut Ui) {
let settings_graph = SettingsGraph::default();
let (g, sim) = generate(&settings_graph);
self.g = g;
self.sim = sim;
self.settings_graph = settings_graph;
self.last_events = Vec::default();
GraphView::<(), (), Directed, DefaultIx>::reset_metadata(ui);
}
fn handle_events(&mut self) {
self.event_consumer.try_iter().for_each(|e| {
if self.last_events.len() > EVENTS_LIMIT {
self.last_events.remove(0);
}
self.last_events.push(serde_json::to_string(&e).unwrap());
match e {
Event::Pan(payload) => match self.pan {
Some(pan) => {
self.pan = Some([pan[0] + payload.diff[0], pan[1] + payload.diff[1]]);
}
None => {
self.pan = Some(payload.diff);
}
},
Event::Zoom(z) => {
match self.zoom {
Some(zoom) => {
self.zoom = Some(zoom + z.diff);
}
None => {
self.zoom = Some(z.diff);
}
};
}
Event::NodeMove(payload) => {
let node_id = NodeIndex::new(payload.id);
let diff = Vec3::new(payload.diff[0], payload.diff[1], 0.);
let node = self.sim.get_graph_mut().node_weight_mut(node_id).unwrap();
node.location += diff;
}
_ => {}
}
});
}
fn random_node_idx(&self) -> Option<NodeIndex> {
let nodes_cnt = self.g.node_count();
if nodes_cnt == 0 {
return None;
}
let random_n_idx = rand::thread_rng().gen_range(0..nodes_cnt);
self.g.g.node_indices().nth(random_n_idx)
}
fn random_edge_idx(&self) -> Option<EdgeIndex> {
let edges_cnt = self.g.edge_count();
if edges_cnt == 0 {
return None;
}
let random_e_idx = rand::thread_rng().gen_range(0..edges_cnt);
self.g.g.edge_indices().nth(random_e_idx)
}
fn remove_random_node(&mut self) {
let idx = self.random_node_idx().unwrap();
self.remove_node(idx);
}
fn add_random_node(&mut self) {
let random_n_idx = self.random_node_idx();
if random_n_idx.is_none() {
return;
}
let random_n = self.g.node(random_n_idx.unwrap()).unwrap();
// location of new node is in surrounging of random existing node
let mut rng = rand::thread_rng();
let location = Pos2::new(
random_n.location().x + 10. + rng.gen_range(0. ..50.),
random_n.location().y + 10. + rng.gen_range(0. ..50.),
);
let idx = self.g.add_node_with_location((), location);
let mut sim_node = fdg_sim::Node::new(idx.index().to_string().as_str(), ());
sim_node.location = Vec3::new(location.x, location.y, 0.);
self.sim.get_graph_mut().add_node(sim_node);
}
fn remove_node(&mut self, idx: NodeIndex) {
self.g.remove_node(idx);
self.sim.get_graph_mut().remove_node(idx).unwrap();
// update edges count
self.settings_graph.count_edge = self.g.edge_count();
}
fn add_random_edge(&mut self) {
let random_start = self.random_node_idx().unwrap();
let random_end = self.random_node_idx().unwrap();
self.add_edge(random_start, random_end);
}
fn add_edge(&mut self, start: NodeIndex, end: NodeIndex) {
self.g.add_edge(start, end, ());
self.sim.get_graph_mut().add_edge(start, end, 1.);
}
fn remove_random_edge(&mut self) {
let random_e_idx = self.random_edge_idx();
if random_e_idx.is_none() {
return;
}
let endpoints = self.g.edge_endpoints(random_e_idx.unwrap()).unwrap();
self.remove_edge(endpoints.0, endpoints.1);
}
fn remove_edge(&mut self, start: NodeIndex, end: NodeIndex) {
let (g_idx, _) = self.g.edges_connecting(start, end).next().unwrap();
self.g.remove_edge(g_idx);
let sim_idx = self.sim.get_graph_mut().find_edge(start, end).unwrap();
self.sim.get_graph_mut().remove_edge(sim_idx).unwrap();
}
fn draw_section_app(&mut self, ui: &mut Ui) {
CollapsingHeader::new("App Config")
.default_open(true)
.show(ui, |ui| {
ui.add_space(10.);
ui.label("Simulation");
ui.separator();
ui.horizontal(|ui| {
if ui
.button(match self.simulation_stopped {
true => "start",
false => "stop",
})
.clicked()
{
self.simulation_stopped = !self.simulation_stopped;
};
if ui.button("reset").clicked() {
self.reset_graph(ui);
}
});
ui.add_space(10.);
self.draw_counts_sliders(ui);
ui.add_space(10.);
ui.separator();
});
}
fn draw_section_widget(&mut self, ui: &mut Ui) {
CollapsingHeader::new("Widget")
.default_open(true)
.show(ui, |ui| {
CollapsingHeader::new("Navigation").default_open(true).show(ui, |ui|{
if ui
.checkbox(&mut self.settings_navigation.fit_to_screen_enabled, "fit_to_screen")
.changed()
&& self.settings_navigation.fit_to_screen_enabled
{
self.settings_navigation.zoom_and_pan_enabled = false
};
ui.label("Enable fit to screen to fit the graph to the screen on every frame.");
ui.add_space(5.);
ui.add_enabled_ui(!self.settings_navigation.fit_to_screen_enabled, |ui| {
ui.vertical(|ui| {
ui.checkbox(&mut self.settings_navigation.zoom_and_pan_enabled, "zoom_and_pan");
ui.label("Zoom with ctrl + mouse wheel, pan with middle mouse drag.");
}).response.on_disabled_hover_text("disable fit_to_screen to enable zoom_and_pan");
});
});
CollapsingHeader::new("Style").show(ui, |ui| {
ui.checkbox(&mut self.settings_style.labels_always, "labels_always");
ui.label("Wheter to show labels always or when interacted only.");
});
CollapsingHeader::new("Interaction").show(ui, |ui| {
if ui.checkbox(&mut self.settings_interaction.dragging_enabled, "dragging_enabled").clicked() && self.settings_interaction.dragging_enabled {
self.settings_interaction.node_clicking_enabled = true;
};
ui.label("To drag use LMB click + drag on a node.");
ui.add_space(5.);
ui.add_enabled_ui(!(self.settings_interaction.dragging_enabled || self.settings_interaction.node_selection_enabled || self.settings_interaction.node_selection_multi_enabled), |ui| {
ui.vertical(|ui| {
ui.checkbox(&mut self.settings_interaction.node_clicking_enabled, "node_clicking_enabled");
ui.label("Check click events in last events");
}).response.on_disabled_hover_text("node click is enabled when any of the interaction is also enabled");
});
ui.add_space(5.);
ui.add_enabled_ui(!self.settings_interaction.node_selection_multi_enabled, |ui| {
ui.vertical(|ui| {
if ui.checkbox(&mut self.settings_interaction.node_selection_enabled, "node_selection_enabled").clicked() && self.settings_interaction.node_selection_enabled {
self.settings_interaction.node_clicking_enabled = true;
};
ui.label("Enable select to select nodes with LMB click. If node is selected clicking on it again will deselect it.");
}).response.on_disabled_hover_text("node_selection_multi_enabled enables select");
});
if ui.checkbox(&mut self.settings_interaction.node_selection_multi_enabled, "node_selection_multi_enabled").changed() && self.settings_interaction.node_selection_multi_enabled {
self.settings_interaction.node_clicking_enabled = true;
self.settings_interaction.node_selection_enabled = true;
}
ui.label("Enable multiselect to select multiple nodes.");
ui.add_space(5.);
ui.add_enabled_ui(!(self.settings_interaction.edge_selection_enabled || self.settings_interaction.edge_selection_multi_enabled), |ui| {
ui.vertical(|ui| {
ui.checkbox(&mut self.settings_interaction.edge_clicking_enabled, "edge_clicking_enabled");
ui.label("Check click events in last events");
}).response.on_disabled_hover_text("edge click is enabled when any of the interaction is also enabled");
});
ui.add_space(5.);
ui.add_enabled_ui(!self.settings_interaction.edge_selection_multi_enabled, |ui| {
ui.vertical(|ui| {
if ui.checkbox(&mut self.settings_interaction.edge_selection_enabled, "edge_selection_enabled").clicked() && self.settings_interaction.edge_selection_enabled {
self.settings_interaction.edge_clicking_enabled = true;
};
ui.label("Enable select to select edges with LMB click. If edge is selected clicking on it again will deselect it.");
}).response.on_disabled_hover_text("edge_selection_multi_enabled enables select");
});
if ui.checkbox(&mut self.settings_interaction.edge_selection_multi_enabled, "edge_selection_multi_enabled").changed() && self.settings_interaction.edge_selection_multi_enabled {
self.settings_interaction.edge_clicking_enabled = true;
self.settings_interaction.edge_selection_enabled = true;
}
ui.label("Enable multiselect to select multiple edges.");
});
CollapsingHeader::new("Selected").default_open(true).show(ui, |ui| {
ScrollArea::vertical().auto_shrink([false, true]).max_height(200.).show(ui, |ui| {
self.g.selected_nodes().iter().for_each(|node| {
ui.label(format!("{node:?}"));
});
self.g.selected_edges().iter().for_each(|edge| {
ui.label(format!("{edge:?}"));
});
});
});
CollapsingHeader::new("Last Events").default_open(true).show(ui, |ui| {
ScrollArea::vertical().auto_shrink([false, true]).max_height(200.).show(ui, |ui| {
self.last_events.iter().rev().for_each(|event| {
ui.label(event);
});
});
});
});
}
fn draw_section_debug(&mut self, ui: &mut Ui) {
CollapsingHeader::new("Debug")
.default_open(true)
.show(ui, |ui| {
if let Some(zoom) = self.zoom {
ui.label(format!("zoom: {:.5}", zoom));
};
if let Some(pan) = self.pan {
ui.label(format!("pan: [{:.5}, {:.5}]", pan[0], pan[1]));
};
ui.label(format!("FPS: {:.1}", self.fps));
});
}
fn draw_counts_sliders(&mut self, ui: &mut Ui) {
ui.horizontal(|ui| {
let before = self.settings_graph.count_node as i32;
ui.add(Slider::new(&mut self.settings_graph.count_node, 1..=2500).text("nodes"));
let delta = self.settings_graph.count_node as i32 - before;
(0..delta.abs()).for_each(|_| {
if delta > 0 {
self.add_random_node();
return;
};
self.remove_random_node();
});
});
ui.horizontal(|ui| {
let before = self.settings_graph.count_edge as i32;
ui.add(Slider::new(&mut self.settings_graph.count_edge, 0..=5000).text("edges"));
let delta = self.settings_graph.count_edge as i32 - before;
(0..delta.abs()).for_each(|_| {
if delta > 0 {
self.add_random_edge();
return;
};
self.remove_random_edge();
});
});
}
}
impl App for ConfigurableApp {
fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
egui::SidePanel::right("right_panel")
.min_width(250.)
.show(ctx, |ui| {
ScrollArea::vertical().show(ui, |ui| {
self.draw_section_app(ui);
ui.add_space(10.);
self.draw_section_debug(ui);
ui.add_space(10.);
self.draw_section_widget(ui);
});
});
egui::CentralPanel::default().show(ctx, |ui| {
let settings_interaction = &egui_graphs::SettingsInteraction::new()
.with_node_selection_enabled(self.settings_interaction.node_selection_enabled)
.with_node_selection_multi_enabled(
self.settings_interaction.node_selection_multi_enabled,
)
.with_dragging_enabled(self.settings_interaction.dragging_enabled)
.with_node_clicking_enabled(self.settings_interaction.node_clicking_enabled)
.with_edge_clicking_enabled(self.settings_interaction.edge_clicking_enabled)
.with_edge_selection_enabled(self.settings_interaction.edge_selection_enabled)
.with_edge_selection_multi_enabled(
self.settings_interaction.edge_selection_multi_enabled,
);
let settings_navigation = &egui_graphs::SettingsNavigation::new()
.with_zoom_and_pan_enabled(self.settings_navigation.zoom_and_pan_enabled)
.with_fit_to_screen_enabled(self.settings_navigation.fit_to_screen_enabled)
.with_zoom_speed(self.settings_navigation.zoom_speed);
let settings_style = &egui_graphs::SettingsStyle::new()
.with_labels_always(self.settings_style.labels_always);
ui.add(
&mut GraphView::<_, _, _, _, DefaultNodeShape, DefaultEdgeShape>::new(&mut self.g)
.with_interactions(settings_interaction)
.with_navigations(settings_navigation)
.with_styles(settings_style)
.with_events(&self.event_publisher),
);
});
self.handle_events();
self.sync_graph_with_simulation();
self.update_simulation();
self.update_fps();
}
}
fn generate(settings: &SettingsGraph) -> (Graph<(), (), Directed, DefaultIx>, Simulation<(), f32>) {
let g = generate_random_graph(settings.count_node, settings.count_edge);
let sim = construct_simulation(&g);
(g, sim)
}
fn construct_simulation(g: &Graph<(), (), Directed, DefaultIx>) -> Simulation<(), f32> {
// create force graph
let mut force_graph = ForceGraph::with_capacity(g.g.node_count(), g.g.edge_count());
g.g.node_indices().for_each(|idx| {
let idx = idx.index();
force_graph.add_force_node(format!("{idx}").as_str(), ());
});
g.g.edge_indices().for_each(|idx| {
let (source, target) = g.g.edge_endpoints(idx).unwrap();
force_graph.add_edge(source, target, 1.);
});
// initialize simulation
let mut params = SimulationParameters::default();
let force = fdg_sim::force::fruchterman_reingold_weighted(100., 0.5);
params.set_force(force);
Simulation::from_graph(force_graph, params)
}
fn generate_random_graph(node_count: usize, edge_count: usize) -> Graph<(), ()> {
let mut rng = rand::thread_rng();
let mut graph = StableGraph::new();
// add nodes
for _ in 0..node_count {
graph.add_node(());
}
// add random edges
for _ in 0..edge_count {
let source = rng.gen_range(0..node_count);
let target = rng.gen_range(0..node_count);
graph.add_edge(NodeIndex::new(source), NodeIndex::new(target), ());
}
to_graph(&graph)
}
fn main() {
let native_options = eframe::NativeOptions::default();
run_native(
"egui_graphs_configurable_demo",
native_options,
Box::new(|cc| Box::new(ConfigurableApp::new(cc))),
)
.unwrap();
}

@ -0,0 +1,47 @@
pub struct SettingsGraph {
pub count_node: usize,
pub count_edge: usize,
}
impl Default for SettingsGraph {
fn default() -> Self {
Self {
count_node: 300,
count_edge: 500,
}
}
}
#[derive(Default)]
pub struct SettingsInteraction {
pub dragging_enabled: bool,
pub node_clicking_enabled: bool,
pub node_selection_enabled: bool,
pub node_selection_multi_enabled: bool,
pub edge_clicking_enabled: bool,
pub edge_selection_enabled: bool,
pub edge_selection_multi_enabled: bool,
}
pub struct SettingsNavigation {
pub fit_to_screen_enabled: bool,
pub zoom_and_pan_enabled: bool,
pub screen_padding: f32,
pub zoom_speed: f32,
}
impl Default for SettingsNavigation {
fn default() -> Self {
Self {
screen_padding: 0.3,
zoom_speed: 0.1,
fit_to_screen_enabled: true,
zoom_and_pan_enabled: false,
}
}
}
#[derive(Default)]
pub struct SettingsStyle {
pub labels_always: bool,
}

@ -0,0 +1,11 @@
[package]
name = "egui-graph-viz"
version = "0.1.0"
license = "MIT"
edition = "2021"
[dependencies]
egui = "0.27"
eframe = "0.27"
petgraph = "0.6"
egui_graphs = "0.20.0"

@ -0,0 +1,9 @@
# Interactive
Modification of the [basic example](https://github.com/blitzarx1/egui_graph/tree/master/examples/basic) which shows how easy it is to enable interactivity.
Try to drag around and select some nodes.
## run
```bash
cargo run --release -p interactive
```

@ -0,0 +1,64 @@
use eframe::{run_native, App, CreationContext};
use egui::Context;
use egui_graphs::{
DefaultEdgeShape, DefaultNodeShape, Graph, GraphView, SettingsInteraction, SettingsStyle,
};
use petgraph::stable_graph::StableGraph;
pub struct InteractiveApp {
g: Graph<(), ()>,
}
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| {
let interaction_settings = &SettingsInteraction::new()
.with_dragging_enabled(true)
.with_node_clicking_enabled(true)
.with_node_selection_enabled(true)
.with_node_selection_multi_enabled(true)
.with_edge_clicking_enabled(true)
.with_edge_selection_enabled(true)
.with_edge_selection_multi_enabled(true);
let style_settings = &SettingsStyle::new().with_labels_always(true);
ui.add(
&mut GraphView::<_, _, _, _, DefaultNodeShape, DefaultEdgeShape>::new(&mut self.g)
.with_styles(style_settings)
.with_interactions(interaction_settings),
);
});
}
}
fn generate_graph() -> Graph<(), ()> {
let mut g = StableGraph::new();
let a = g.add_node(());
let b = g.add_node(());
let c = g.add_node(());
g.add_edge(a, a, ());
g.add_edge(a, b, ());
g.add_edge(a, b, ());
g.add_edge(b, c, ());
g.add_edge(c, a, ());
Graph::from(&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();
}

@ -0,0 +1,26 @@
struct AdjacencyGraph {
nodes: Vec<&str>,
adjacencies: HashMap<&str, Vec<&str>>,
}
impl AdjacencyGraph {
fn new() -> Self {
AdjacencyGraph {
nodes: Vec::new(),
adjacencies: HashMap::new(),
}
}
fn add_node(&mut self, node: &str) {
self.nodes.push(node);
self.adjacencies.insert(node, Vec::new());
}
fn add_edge(&mut self, from: &str, to: &str) {
self.adjacencies.get_mut(from).unwrap().push(to);
}
fn neighbors(&self, node: &str) -> Option<&Vec<&str>> {
self.adjacencies.get(node)
}
}
Loading…
Cancel
Save