Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rainbow edges example #206

Merged
merged 1 commit into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

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

2 changes: 0 additions & 2 deletions examples/demo/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ pub struct SettingsInteraction {
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,
Expand Down
4 changes: 1 addition & 3 deletions examples/flex_nodes/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use eframe::{run_native, App, CreationContext};
use egui::{CentralPanel, Context, SidePanel, TextEdit};
use egui_graphs::{
DefaultEdgeShape, Graph, GraphView, SettingsInteraction, SettingsNavigation, SettingsStyle,
};
use egui_graphs::{DefaultEdgeShape, Graph, GraphView, SettingsInteraction, SettingsNavigation};
use node::NodeShapeFlex;
use petgraph::{
stable_graph::{DefaultIx, EdgeIndex, NodeIndex, StableGraph},
Expand Down
10 changes: 10 additions & 0 deletions examples/rainbow_edges/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "rainbow_edges"
version = "0.1.0"
edition = "2021"

[dependencies]
egui_graphs = { path = "../../" }
egui = "0.28"
eframe = "0.28"
petgraph = "0.6"
7 changes: 7 additions & 0 deletions examples/rainbow_edges/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Rainbow Edges
Example demonstrates the possibility of drawing custom edges shapes.

## run
```bash
cargo run --release -p rainbow_edges
```
107 changes: 107 additions & 0 deletions examples/rainbow_edges/src/edge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use egui::{Color32, Pos2, Shape, Stroke, Vec2};
use egui_graphs::{DefaultEdgeShape, DisplayEdge, DisplayNode, DrawContext, EdgeProps, Node};
use petgraph::{stable_graph::IndexType, EdgeType};

const TIP_ANGLE: f32 = std::f32::consts::TAU / 30.;
const TIP_SIZE: f32 = 15.;
const COLORS: [Color32; 7] = [
Color32::RED,
Color32::from_rgb(255, 102, 0),
Color32::YELLOW,
Color32::GREEN,
Color32::from_rgb(2, 216, 233),
Color32::BLUE,
Color32::from_rgb(91, 10, 145),
];

#[derive(Clone)]
pub struct RainbowEdgeShape {
default_impl: DefaultEdgeShape,
}

impl<E: Clone> From<EdgeProps<E>> for RainbowEdgeShape {
fn from(props: EdgeProps<E>) -> Self {
Self {
default_impl: DefaultEdgeShape::from(props),
}
}
}

impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, Ix>>
DisplayEdge<N, E, Ty, Ix, D> for RainbowEdgeShape
{
fn shapes(
&mut self,
start: &Node<N, E, Ty, Ix, D>,
end: &Node<N, E, Ty, Ix, D>,
ctx: &DrawContext,
) -> Vec<egui::Shape> {
let mut res = vec![];
let (start, end) = (start.location(), end.location());
let (x_dist, y_dist) = (end.x - start.x, end.y - start.y);
let (dx, dy) = (x_dist / COLORS.len() as f32, y_dist / COLORS.len() as f32);
let d_vec = Vec2::new(dx, dy);

let mut stroke = Stroke::default();
let mut points_line;

for (i, color) in COLORS.iter().enumerate() {
stroke = Stroke::new(self.default_impl.width, *color);
points_line = vec![
start + i as f32 * d_vec,
end - (COLORS.len() - i - 1) as f32 * d_vec,
];

stroke.width = ctx.meta.canvas_to_screen_size(stroke.width);
points_line = points_line
.iter()
.map(|p| ctx.meta.canvas_to_screen_pos(*p))
.collect();
res.push(Shape::line_segment(
[points_line[0], points_line[1]],
stroke,
));
}

let tip_dir = (end - start).normalized();

let arrow_tip_dir_1 = rotate_vector(tip_dir, TIP_ANGLE) * TIP_SIZE;
let arrow_tip_dir_2 = rotate_vector(tip_dir, -TIP_ANGLE) * TIP_SIZE;

let tip_start_1 = end - arrow_tip_dir_1;
let tip_start_2 = end - arrow_tip_dir_2;

let mut points_tip = vec![end, tip_start_1, tip_start_2];

points_tip = points_tip
.iter()
.map(|p| ctx.meta.canvas_to_screen_pos(*p))
.collect();

res.push(Shape::convex_polygon(
points_tip,
stroke.color,
Stroke::default(),
));

res
}

fn update(&mut self, _: &egui_graphs::EdgeProps<E>) {}

fn is_inside(
&self,
start: &Node<N, E, Ty, Ix, D>,
end: &Node<N, E, Ty, Ix, D>,
pos: Pos2,
) -> bool {
self.default_impl.is_inside(start, end, pos)
}
}

/// rotates vector by angle
fn rotate_vector(vec: Vec2, angle: f32) -> Vec2 {
let cos = angle.cos();
let sin = angle.sin();
Vec2::new(cos * vec.x - sin * vec.y, sin * vec.x + cos * vec.y)
}
55 changes: 55 additions & 0 deletions examples/rainbow_edges/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use edge::RainbowEdgeShape;
use eframe::{run_native, App, CreationContext};
use egui::Context;
use egui_graphs::{DefaultNodeShape, Graph, GraphView};
use petgraph::{csr::DefaultIx, stable_graph::StableGraph, Directed};

mod edge;

pub struct RainbowEdgesApp {
g: Graph<(), (), Directed, DefaultIx, DefaultNodeShape, RainbowEdgeShape>,
}

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

impl App for RainbowEdgesApp {
fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.add(
&mut GraphView::<_, _, _, _, DefaultNodeShape, RainbowEdgeShape>::new(&mut self.g)
.with_interactions(
&egui_graphs::SettingsInteraction::default().with_dragging_enabled(true),
),
);
});
}
}

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

let a = g.add_node(());
let b = g.add_node(());
let c = g.add_node(());

g.add_edge(a, b, ());
g.add_edge(b, c, ());
g.add_edge(c, a, ());

g
}

fn main() {
let native_options = eframe::NativeOptions::default();
run_native(
"egui_graphs_rainbow_edges_demo",
native_options,
Box::new(|cc| Ok(Box::new(RainbowEdgesApp::new(cc)))),
)
.unwrap();
}
25 changes: 12 additions & 13 deletions src/draw/displays_default/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, I

if start.id() == end.id() {
// draw loop
let size = node_size(start);
let size = node_size(start, Vec2::new(-1., 0.));
let mut line_looped_shapes = EdgeShapeBuilder::new(stroke)
.looped(start.location(), size, self.loop_size, self.order)
.with_scaler(ctx.meta)
Expand All @@ -92,7 +92,7 @@ impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, I

let line_looped = match line_looped_shapes.pop().unwrap() {
Shape::CubicBezier(cubic) => cubic,
_ => panic!("Invalid shape type"),
_ => panic!("invalid shape type"),
};

// TODO: export to func
Expand Down Expand Up @@ -141,7 +141,7 @@ impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, I

// TODO: export to func
if label_visible {
let size = (node_size(start) + node_size(end)) / 2.;
let size = (node_size(start, dir) + node_size(end, dir)) / 2.;
let galley = ctx.ctx.fonts(|f| {
f.layout_no_wrap(
self.label_text.clone(),
Expand Down Expand Up @@ -183,13 +183,12 @@ impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, I
let curved_shapes = builder.build();
let line_curved = match curved_shapes.first() {
Some(Shape::CubicBezier(curve)) => curve,
_ => panic!("Invalid shape type"),
_ => panic!("invalid shape type"),
};
res.extend(curved_shapes.clone());

// TODO: export to func
if label_visible {
let size = (node_size(start) + node_size(end)) / 2.;
let size = (node_size(start, dir) + node_size(end, dir)) / 2.;
let galley = ctx.ctx.fonts(|f| {
f.layout_no_wrap(
self.label_text.clone(),
Expand Down Expand Up @@ -231,15 +230,15 @@ impl DefaultEdgeShape {
node: &Node<N, E, Ty, Ix, D>,
pos: Pos2,
) -> bool {
let node_size = node_size(node);
let node_size = node_size(node, Vec2::new(-1., 0.));

let shape = EdgeShapeBuilder::new(Stroke::new(self.width, Color32::default()))
.looped(node.location(), node_size, self.loop_size, self.order)
.build();

match shape.first() {
Some(Shape::CubicBezier(cubic)) => is_point_on_curve(pos, cubic.clone()),
_ => panic!("Invalid shape type"),
_ => panic!("invalid shape type"),
}
}

Expand Down Expand Up @@ -269,20 +268,20 @@ impl DefaultEdgeShape {
.build();
let curved_shape = match curved_shapes.first() {
Some(Shape::CubicBezier(curve)) => curve.clone(),
_ => panic!("Invalid shape type"),
_ => panic!("invalid shape type"),
};

is_point_on_curve(pos, curved_shape)
}
}

// TOOD: export this func as common drawing func
// TODO: export this func as common drawing func
fn node_size<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, Ix>>(
node: &Node<N, E, Ty, Ix, D>,
dir: Vec2,
) -> f32 {
let left_dir = Vec2::new(-1., 0.);
let connector_left = node.display().closest_boundary_point(left_dir);
let connector_right = node.display().closest_boundary_point(-left_dir);
let connector_left = node.display().closest_boundary_point(dir);
let connector_right = node.display().closest_boundary_point(-dir);

(connector_right.x - connector_left.x) / 2.
}
Expand Down
Loading