You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

625 lines
23 KiB
Rust

use std::collections::HashMap;
use std::env;
6 months ago
use std::time::Instant;
use asd::gfa::{Entry, Orientation};
use asd::parser;
6 months ago
use crossbeam::channel::{unbounded, Receiver, Sender};
use eframe::{run_native, App, CreationContext};
use egui::{CollapsingHeader, Context, Pos2, ScrollArea, Ui};
6 months ago
use egui_graphs::events::Event;
use egui_graphs::{DefaultEdgeShape, DefaultNodeShape, Graph, GraphView};
6 months ago
use fdg_sim::glam::Vec3;
use fdg_sim::{ForceGraph, ForceGraphHelper, Simulation, SimulationParameters};
use petgraph::stable_graph::{DefaultIx, NodeIndex, StableGraph};
6 months ago
use petgraph::Directed;
use settings::{SettingsInteraction, SettingsNavigation, SettingsStyle};
6 months ago
mod settings;
// const SIMULATION_DT: f32 = 0.035;
const SIMULATION_DT: f32 = 0.075;
6 months ago
const EVENTS_LIMIT: usize = 100;
pub struct ConfigurableApp {
g: Graph<(String, Orientation), (), Directed, DefaultIx>,
6 months ago
sim: Simulation<(), f32>,
// settings_graph: SettingsGraph,
6 months ago
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();
6 months ago
let (event_publisher, event_consumer) = unbounded();
Self {
g,
sim,
event_consumer,
event_publisher,
// settings_graph,
6 months ago
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();
6 months ago
self.g = g;
self.sim = sim;
// self.settings_graph = settings_graph;
6 months ago
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;
// }
6 months ago
// let random_n_idx = rand::thread_rng().gen_range(0..nodes_cnt);
// self.g.g.node_indices().nth(random_n_idx)
// }
6 months ago
// fn random_edge_idx(&self) -> Option<EdgeIndex> {
// let edges_cnt = self.g.edge_count();
// if edges_cnt == 0 {
// return None;
// }
6 months ago
// let random_e_idx = rand::thread_rng().gen_range(0..edges_cnt);
// self.g.g.edge_indices().nth(random_e_idx)
// }
6 months ago
// fn remove_random_node(&mut self) {
// let idx = self.random_node_idx().unwrap();
// self.remove_node(idx);
// }
6 months ago
// fn add_random_node(&mut self) {
// let random_n_idx = self.random_node_idx();
// if random_n_idx.is_none() {
// return;
// }
6 months ago
// let random_n = self.g.node(random_n_idx.unwrap()).unwrap();
6 months ago
// // 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.),
// );
6 months ago
// let idx = self.g.add_node_with_location((), location);
6 months ago
// 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);
// }
6 months ago
// fn remove_node(&mut self, idx: NodeIndex) {
// self.g.remove_node(idx);
6 months ago
// self.sim.get_graph_mut().remove_node(idx).unwrap();
6 months ago
// // update edges count
// self.settings_graph.count_edge = self.g.edge_count();
// }
6 months ago
// fn add_random_edge(&mut self) {
// let random_start = self.random_node_idx().unwrap();
// let random_end = self.random_node_idx().unwrap();
6 months ago
// self.add_edge(random_start, random_end);
// }
6 months ago
// fn add_edge(&mut self, start: NodeIndex, end: NodeIndex) {
// self.g.add_edge(start, end, ());
6 months ago
// self.sim.get_graph_mut().add_edge(start, end, 1.);
// }
6 months ago
// 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();
6 months ago
// self.remove_edge(endpoints.0, endpoints.1);
// }
6 months ago
// 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);
6 months ago
// let sim_idx = self.sim.get_graph_mut().find_edge(start, end).unwrap();
// self.sim.get_graph_mut().remove_edge(sim_idx).unwrap();
// }
6 months ago
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.);
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));
ui.label(format!("Nodes: {}", self.g.node_count()));
ui.label(format!("Edges: {}", self.g.edge_count()));
6 months ago
});
}
// 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();
// // });
// // });
// }
6 months ago
}
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() -> (
Graph<(String, Orientation), (), Directed, DefaultIx>,
Simulation<(), f32>,
) {
let mut g: StableGraph<(String, Orientation), ()> = StableGraph::new();
let file = std::fs::File::open(env::args().nth(1).expect("missing gfa file argument")).unwrap();
let entries = parser::parse_source(file).unwrap();
let mut index_map = HashMap::new();
for entry in entries {
// println!("{:?}", entry);
if let Entry::Link {
from,
from_orient,
to,
to_orient,
} = entry
{
// add first node if not present
let a = index_map
.entry(from.clone())
.or_insert_with(|| g.add_node((from.clone(), from_orient)))
.to_owned();
// add second node if not present
let b = index_map
.entry(to.clone())
.or_insert_with(|| g.add_node((to.clone(), to_orient)))
.to_owned();
g.add_edge(a, b, ());
}
}
let g = Graph::from(&g);
6 months ago
let sim = construct_simulation(&g);
(g, sim)
}
fn construct_simulation(
g: &Graph<(String, Orientation), (), Directed, DefaultIx>,
) -> Simulation<(), f32> {
6 months ago
// 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.75);
6 months ago
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();
6 months ago
// // add nodes
// for _ in 0..node_count {
// graph.add_node(());
// }
6 months ago
// // add random edges
// for _ in 0..edge_count {
// let source = rng.gen_range(0..node_count);
// let target = rng.gen_range(0..node_count);
6 months ago
// graph.add_edge(NodeIndex::new(source), NodeIndex::new(target), ());
// }
6 months ago
// to_graph(&graph)
// }
6 months ago
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();
}