diff --git a/Cargo.lock b/Cargo.lock index aded64e..b71dedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -729,7 +729,7 @@ dependencies = [ [[package]] name = "egui_graphs" -version = "0.0.12" +version = "0.0.13" dependencies = [ "egui", "petgraph", diff --git a/Cargo.toml b/Cargo.toml index f83bd8f..69d7dff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egui_graphs" -version = "0.0.12" +version = "0.0.13" authors = ["Dmitrii Samsonov "] license = "MIT" homepage = "https://github.com/blitzarx1/egui_graphs" diff --git a/example/src/main.rs b/example/src/main.rs index 838a2ff..56e8fdd 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, time::Instant}; use eframe::{run_native, App, CreationContext}; +use egui::plot::{Line, Plot, PlotPoints}; use egui::{Context, ScrollArea, Vec2}; use egui_graphs::{Changes, Edge, Elements, GraphView, Node, Settings}; use fdg_sim::glam::Vec3; @@ -21,10 +22,12 @@ pub struct ExampleApp { selected: Vec, + simulation_stopped: bool, + + fps: f64, + fps_history: Vec, last_update_time: Instant, frames_last_time_span: usize, - fps: f32, - simulation_stopped: bool, } impl ExampleApp { @@ -38,10 +41,12 @@ impl ExampleApp { selected: Default::default(), + simulation_stopped: false, + + fps: 0., + fps_history: Default::default(), last_update_time: Instant::now(), frames_last_time_span: 0, - fps: 0., - simulation_stopped: false, } } @@ -103,8 +108,13 @@ impl ExampleApp { 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 f32 / elapsed.as_secs_f32(); + self.fps = self.frames_last_time_span as f64 / elapsed.as_secs_f64(); self.frames_last_time_span = 0; + + self.fps_history.push(self.fps); + if self.fps_history.len() > 100 { + self.fps_history.remove(0); + } } } @@ -218,6 +228,26 @@ impl App for ExampleApp { egui::TopBottomPanel::bottom("bottom_panel").show_inside(ui, |ui| { ui.add_space(5.); ui.label(format!("fps: {:.1}", self.fps)); + + let sin: PlotPoints = self + .fps_history + .iter() + .enumerate() + .map(|(i, val)| [i as f64, *val]) + .collect(); + let line = Line::new(sin); + Plot::new("my_plot") + .height(100.) + .show_x(false) + .show_y(false) + .show_background(false) + .show_axes([false, true]) + .allow_boxed_zoom(false) + .allow_double_click_reset(false) + .allow_drag(false) + .allow_scroll(false) + .allow_zoom(false) + .show(ui, |plot_ui| plot_ui.line(line)); }); }); diff --git a/src/changes.rs b/src/changes.rs index 5645920..b1c7199 100644 --- a/src/changes.rs +++ b/src/changes.rs @@ -73,7 +73,7 @@ impl Changes { } } -/// stores deltas to the nodes properties +/// stores changes to the node properties #[derive(Default, Clone)] pub struct ChangesNode { pub location: Option, @@ -104,7 +104,7 @@ impl ChangesNode { } } -/// stores deltas to the edges properties +/// stores changes to the edge properties #[derive(Default, Clone)] pub struct ChangesEdge { pub color: Option, diff --git a/src/elements.rs b/src/elements.rs index 96f2378..efdcf3e 100644 --- a/src/elements.rs +++ b/src/elements.rs @@ -2,9 +2,11 @@ use std::collections::HashMap; use egui::{Color32, Vec2}; +/// Elements represents the collection of all nodes and edges in a graph. +/// It is passed to the GraphView widget and is used to draw the graph. pub struct Elements { - pub(crate) nodes: HashMap, - pub(crate) edges: HashMap<(usize, usize), Vec>, + nodes: HashMap, + edges: HashMap<(usize, usize), Vec>, } impl Elements { @@ -12,6 +14,33 @@ impl Elements { Self { nodes, edges } } + pub fn get_node(&self, idx: &usize) -> Option<&Node> { + self.nodes.get(idx) + } + + pub fn get_nodes(&self) -> &HashMap { + &self.nodes + } + + pub fn get_edges(&self) -> &HashMap<(usize, usize), Vec> { + &self.edges + } + + pub fn get_edge(&self, idx: &(usize, usize, usize)) -> Option<&Edge> { + self.edges.get(&(idx.0, idx.1))?.get(idx.2) + } + + /// deletes node and all edges connected to it + pub fn delete_node(&mut self, idx: &usize) { + self.nodes.remove(idx); + self.edges.retain(|k, _| k.0 != *idx && k.1 != *idx); + } + + /// deletes edge + pub fn delete_edge(&mut self, idx: &(usize, usize, usize)) { + self.edges.get_mut(&(idx.0, idx.1)).unwrap().remove(idx.2); + } + pub fn get_node_mut(&mut self, idx: &usize) -> Option<&mut Node> { self.nodes.get_mut(idx) } diff --git a/src/graph_view.rs b/src/graph_view.rs index 3c57887..75d5648 100644 --- a/src/graph_view.rs +++ b/src/graph_view.rs @@ -48,6 +48,9 @@ impl<'a> GraphView<'a> { self.changes.borrow().clone() } + /// should be called to clear cached graph metadata, for example + /// in case when you want to show completely different graph from the one + /// in the last frame pub fn reset_state(ui: &mut Ui) { State::default().store(ui); } @@ -96,7 +99,7 @@ fn get_bounds(elements: &Elements) -> (Vec2, Vec2) { let mut max_x = MIN; let mut max_y = MIN; - elements.nodes.iter().for_each(|(_, n)| { + elements.get_nodes().iter().for_each(|(_, n)| { if n.location.x < min_x { min_x = n.location.x; }; @@ -122,13 +125,13 @@ fn rotate_vector(vec: Vec2, angle: f32) -> Vec2 { fn draw(p: &Painter, elements: &Elements, zoom: f32, pan: Vec2) { draw_edges(p, elements, zoom, pan); - draw_nodes(p, &elements.nodes, zoom, pan); + draw_nodes(p, elements, zoom, pan); } fn draw_edges(p: &Painter, elements: &Elements, zoom: f32, pan: Vec2) { let angle = std::f32::consts::TAU / 50.; - elements.edges.iter().for_each(|(_, edges)| { + elements.get_edges().iter().for_each(|(_, edges)| { let edges_count = edges.len(); let mut sames = HashMap::with_capacity(edges_count); @@ -136,13 +139,11 @@ fn draw_edges(p: &Painter, elements: &Elements, zoom: f32, pan: Vec2) { let edge = e.screen_transform(zoom); let start_node = elements - .nodes - .get(&edge.start) + .get_node(&edge.start) .unwrap() .screen_transform(zoom, pan); let end_node = elements - .nodes - .get(&edge.end) + .get_node(&edge.end) .unwrap() .screen_transform(zoom, pan); @@ -235,8 +236,8 @@ fn draw_edges(p: &Painter, elements: &Elements, zoom: f32, pan: Vec2) { }); } -fn draw_nodes(p: &Painter, nodes: &HashMap, zoom: f32, pan: Vec2) { - nodes.iter().for_each(|(_, n)| { +fn draw_nodes(p: &Painter, elements: &Elements, zoom: f32, pan: Vec2) { + elements.get_nodes().iter().for_each(|(_, n)| { let node = n.screen_transform(zoom, pan); draw_node(p, &node) }); @@ -324,9 +325,10 @@ fn handle_zoom_and_pan(ui: &Ui, response: &Response, state: &State) -> (f32, Vec (new_zoom, new_pan) } -// TODO: optimize this full scan run +// TODO: optimize this full scan run with quadtree or similar +// need to modify `crate::elements::Elements` to store nodes in a quadtree fn node_by_pos<'a>(elements: &'a Elements, state: &State, pos: Pos2) -> Option<(usize, &'a Node)> { - let node_props = elements.nodes.iter().find(|(_, n)| { + let node_props = elements.get_nodes().iter().find(|(_, n)| { let node = n.screen_transform(state.zoom, state.pan); (node.location - pos.to_vec2()).length() <= node.radius }); @@ -346,14 +348,14 @@ fn handle_drags( ) { if response.drag_started() { if let Some((idx, _)) = node_by_pos(elements, state, response.hover_pos().unwrap()) { - changes.select_node(&idx, elements.nodes.get(&idx).unwrap()); + changes.select_node(&idx, elements.get_node(&idx).unwrap()); state.set_dragged_node(idx); } } if response.dragged() && state.get_dragged_node().is_some() { let node_idx_dragged = state.get_dragged_node().unwrap(); - let node_dragged = elements.nodes.get(&node_idx_dragged).unwrap(); + let node_dragged = elements.get_node(&node_idx_dragged).unwrap(); let delta_in_graph_coords = response.drag_delta() / state.zoom; changes.move_node(&node_idx_dragged, node_dragged, delta_in_graph_coords); @@ -361,7 +363,7 @@ fn handle_drags( if response.drag_released() && state.get_dragged_node().is_some() { let idx = &state.get_dragged_node().unwrap(); - changes.deselect_node(idx, elements.nodes.get(idx).unwrap()); + changes.deselect_node(idx, elements.get_node(idx).unwrap()); state.unset_dragged_node(); } } @@ -378,16 +380,17 @@ fn handle_clicks( let node = node_by_pos(elements, state, response.hover_pos().unwrap()); if node.is_none() { - elements.nodes.iter().for_each(|(idx, n)| { + // TODO: optimize this. keep selected nodes in state to quickly manage them + elements.get_nodes().iter().for_each(|(idx, n)| { if !n.selected { return; } - changes.deselect_node(idx, elements.nodes.get(idx).unwrap()); + changes.deselect_node(idx, n); }); return; } - let (idx, _) = node.unwrap(); - changes.select_node(&idx, elements.nodes.get(&idx).unwrap()); + let (idx, n) = node.unwrap(); + changes.select_node(&idx, n); }