Skip to content

Commit

Permalink
Custom drawing function refactor (#97)
Browse files Browse the repository at this point in the history
* Encapsulated widget state to `WidgetState` struct, for usage in custom
drawing functions
* Added custom edges drawing function
* Added demo with edges labels using custom edges drawing function
(#96)
  • Loading branch information
blitzarx1 authored Oct 21, 2023
1 parent 7286a9c commit 449be3b
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 286 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "egui_graphs"
version = "0.14.0"
version = "0.15.0"
authors = ["Dmitrii Samsonov <[email protected]>"]
license = "MIT"
homepage = "https://github.com/blitzarx1/egui_graphs"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The project implements a Widget for the egui framework, enabling easy visualizat
- [x] Style configuration via egui context styles;
- [x] Dark/Light theme support via egui context styles;
- [x] Events reporting to extend the graph functionality by the user handling them;
- [ ] Edge labels (for the moment there is a `custom_draw` which demonstrates labels drawing for edges);

## Status
The project is on track for a stable release v1.0.0. For the moment, breaking releases are still possible.
Expand Down
1 change: 0 additions & 1 deletion examples/configurable/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::time::Instant;

use crossbeam::channel::{unbounded, Receiver, Sender};
use eframe::glow::WAIT_FAILED;
use eframe::{run_native, App, CreationContext};
use egui::{CollapsingHeader, Context, ScrollArea, Slider, Ui, Vec2};
use egui_graphs::events::Event;
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_draw/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Custom draw
Basic example which demonstrates the usage of `GraphView` widget.
Example demonstrates how to use custom drawing functions for edge and nodes. Here we draw nodes as squares and adding edge labels for the standard edge shape.

## run
```bash
Expand Down
123 changes: 87 additions & 36 deletions examples/custom_draw/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use eframe::{run_native, App, CreationContext};
use egui::{
epaint::TextShape, Context, FontFamily, FontId, Pos2, Rect, Rounding, Shape, Stroke, Vec2,
epaint::TextShape, Context, FontFamily, FontId, Rect, Rounding, Shape, Stroke, Vec2,
};
use egui_graphs::{Graph, GraphView};
use egui_graphs::{default_edges_draw, Graph, GraphView, SettingsInteraction};
use petgraph::{stable_graph::StableGraph, Directed};

pub struct BasicApp {
Expand All @@ -19,40 +19,91 @@ impl BasicApp {
impl App for BasicApp {
fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.add(&mut GraphView::new(&mut self.g).with_custom_node_draw(
|ctx, n, meta, style, l| {
// lets draw a rect with label in the center for every node

// find node center location on the screen coordinates
let node_center_loc = n.screen_location(meta).to_pos2();

// find node radius accounting for current zoom level; we will use it as a reference for the rect and label sizes
let rad = n.screen_radius(meta, style);

// first create rect shape
let size = Vec2::new(rad * 1.5, rad * 1.5);
let rect = Rect::from_center_size(node_center_loc, size);
let shape_rect = Shape::rect_stroke(
rect,
Rounding::default(),
Stroke::new(1., n.color(ctx)),
);

// then create shape for the label placing it in the center of the rect
let color = ctx.style().visuals.text_color();
let galley = ctx.fonts(|f| {
f.layout_no_wrap(n.label(), FontId::new(rad, FontFamily::Monospace), color)
});
// we need to offset a bit to place the label in the center of the rect
let label_loc =
Pos2::new(node_center_loc.x - rad / 2., node_center_loc.y - rad / 2.);
let shape_label = TextShape::new(label_loc, galley);

// add shapes to the drawing layers; the drawing process is happening in the widget lifecycle.
l.add(shape_rect);
l.add(shape_label);
},
));
ui.add(
&mut GraphView::new(&mut self.g)
.with_interactions(
&SettingsInteraction::default()
.with_dragging_enabled(true)
.with_selection_enabled(true),
)
.with_custom_node_draw(|ctx, n, state, l| {
// lets draw a rect with label in the center for every node

// find node center location on the screen coordinates
let node_center_loc = n.screen_location(state.meta).to_pos2();

// find node radius accounting for current zoom level; we will use it as a reference for the rect and label sizes
let rad = n.screen_radius(state.meta, state.style);

// first create rect shape
let size = Vec2::new(rad * 1.5, rad * 1.5);
let rect = Rect::from_center_size(node_center_loc, size);
let shape_rect = Shape::rect_stroke(
rect,
Rounding::default(),
Stroke::new(1., n.color(ctx)),
);

// add rect to the layers
l.add(shape_rect);

// then create label
let color = ctx.style().visuals.text_color();
let galley = ctx.fonts(|f| {
f.layout_no_wrap(
n.label(),
FontId::new(rad, FontFamily::Monospace),
color,
)
});

// we need to offset label by half its size to place it in the center of the rect
let offset = Vec2::new(-galley.size().x / 2., -galley.size().y / 2.);

// create the shape and add it to the layers
let shape_label = TextShape::new(node_center_loc + offset, galley);
l.add(shape_label);
})
.with_custom_edge_draw(|ctx, bounds, edges, state, l| {
// draw edges with labels in the middle

// draw default edges
default_edges_draw(ctx, bounds, edges, state, l);

// get start and end nodes
let n_start = state.g.node(bounds.0).unwrap();
let n_end = state.g.node(bounds.1).unwrap();

// get start and end node locations
let loc_start = n_start.screen_location(state.meta);
let loc_end = n_end.screen_location(state.meta);

// compute edge center location
let center_loc = (loc_start + loc_end) / 2.;

// let label be the average of bound nodes sizes
let size = (n_start.screen_radius(state.meta, state.style)
+ n_end.screen_radius(state.meta, state.style))
/ 2.;

// create label
let color = ctx.style().visuals.text_color();
let galley = ctx.fonts(|f| {
f.layout_no_wrap(
format!("{}->{}", n_start.label(), n_end.label()),
FontId::new(size, FontFamily::Monospace),
color,
)
});

// we need to offset half the label size to place it in the center of the edge
let offset = Vec2::new(-galley.size().x / 2., -galley.size().y / 2.);

// create the shape and add it to the layers
let shape_label = TextShape::new((center_loc + offset).to_pos2(), galley);
l.add(shape_label);
}),
);
});
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/draw/custom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use egui::Context;
use petgraph::{stable_graph::NodeIndex, EdgeType};

use crate::{Edge, Graph, Metadata, Node, SettingsStyle};

use super::Layers;

/// Contains all the data about current widget state which is needed for custom drawing functions.
pub struct WidgetState<'a, N: Clone, E: Clone, Ty: EdgeType> {
pub g: &'a Graph<N, E, Ty>,
pub style: &'a SettingsStyle,
pub meta: &'a Metadata,
}

/// Allows to fully customize what shape would be drawn for node.
/// The function is called for every node in the graph.
///
/// Parameters:
/// - egui context, is needed for computing node props and styles;
/// - node reference, contains all node data;
/// - widget state with references to graph, style and metadata;
/// - when you create a shape, add it to the layers.
pub type FnCustomNodeDraw<N, E, Ty> =
fn(&Context, n: &Node<N>, &WidgetState<N, E, Ty>, &mut Layers);

/// Allows to fully customize what shape would be drawn for an edge.
/// The function is **called once for every node pair** which has edges connecting them. So make sure you have drawn all the edges which are passed to the function.
///
/// Parameters:
/// - egui context, is needed for computing node props and styles;
/// - start node index and end node index;
/// - vector of edges, all edges between start and end nodes;
/// - widget state with references to graph, style and metadata;
/// - when you create a shape, add it to the layers.
pub type FnCustomEdgeDraw<N, E, Ty> =
fn(&Context, (NodeIndex, NodeIndex), Vec<&Edge<E>>, &WidgetState<N, E, Ty>, &mut Layers);
Loading

0 comments on commit 449be3b

Please sign in to comment.