use edge::RainbowEdgeShape;
use eframe::{run_native, App, CreationContext};
use egui_graphs::{generate_simple_digraph, DefaultNodeShape, Graph, GraphView};
use petgraph::{csr::DefaultIx, Directed};
pub struct RainbowEdgesApp {
g: Graph<(), (), Directed, DefaultIx, DefaultNodeShape, RainbowEdgeShape>,
}
impl RainbowEdgesApp {
fn new(_: &CreationContext<'_>) -> Self {
let g = generate_simple_digraph();
Self { g: Graph::from(&g) }
}
}
impl App for RainbowEdgesApp {
fn ui(&mut self, ui: &mut egui::Ui, _: &mut eframe::Frame) {
egui::CentralPanel::default().show_inside(ui, |ui| {
ui.add(
&mut GraphView::<_, _, _, _, _, RainbowEdgeShape>::new(&mut self.g)
.with_interactions(
&egui_graphs::SettingsInteraction::default().with_dragging_enabled(true),
),
);
});
}
}
fn main() {
let native_options = eframe::NativeOptions::default();
run_native(
"rainbow_edges",
native_options,
Box::new(|cc| Ok(Box::new(RainbowEdgesApp::new(cc)))),
)
.unwrap();
}
mod edge {
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)
}
}
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)
}
}