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

Add support for Bezier-curve multi (self-)edges #8256

Merged
merged 13 commits into from
Dec 3, 2024
22 changes: 11 additions & 11 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1917,7 +1917,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ecolor"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"bytemuck",
"emath",
Expand All @@ -1933,7 +1933,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f"
[[package]]
name = "eframe"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"ahash",
"bytemuck",
Expand Down Expand Up @@ -1972,7 +1972,7 @@ dependencies = [
[[package]]
name = "egui"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"accesskit",
"ahash",
Expand All @@ -1989,7 +1989,7 @@ dependencies = [
[[package]]
name = "egui-wgpu"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -2008,7 +2008,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"accesskit_winit",
"ahash",
Expand Down Expand Up @@ -2050,7 +2050,7 @@ dependencies = [
[[package]]
name = "egui_extras"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"ahash",
"egui",
Expand All @@ -2067,7 +2067,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -2085,7 +2085,7 @@ dependencies = [
[[package]]
name = "egui_kittest"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"dify",
"egui",
Expand Down Expand Up @@ -2154,7 +2154,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "emath"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"bytemuck",
"serde",
Expand Down Expand Up @@ -2270,7 +2270,7 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"
dependencies = [
"ab_glyph",
"ahash",
Expand All @@ -2289,7 +2289,7 @@ dependencies = [
[[package]]
name = "epaint_default_fonts"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=6833cf56e17d2581163c43eb36dc3ee5d758771b#6833cf56e17d2581163c43eb36dc3ee5d758771b"

[[package]]
name = "equivalent"
Expand Down
15 changes: 7 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -558,14 +558,13 @@ significant_drop_tightening = "allow" # An update of parking_lot made this trigg
# As a last resport, patch with a commit to our own repository.
# ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk.

ecolor = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
eframe = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
emath = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27

egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
ecolor = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02
eframe = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02
egui = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02
egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02
emath = { git = "https://github.com/emilk/egui.git", rev = "6833cf56e17d2581163c43eb36dc3ee5d758771b" } # egui master 2024-12-02

# Useful while developing:
# ecolor = { path = "../../egui/crates/ecolor" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,23 @@ impl std::fmt::Debug for NodeId {
write!(f, "NodeIndex({:?}@{:?})", self.node_hash, self.entity_hash)
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EdgeId {
// TODO(grtlr): Consider something more storage efficient here
pub source: NodeId,
pub target: NodeId,
}

impl EdgeId {
pub fn self_edge(node: NodeId) -> Self {
Self {
source: node,
target: node,
}
}

pub fn is_self_edge(&self) -> bool {
self.source == self.target
}
}
29 changes: 15 additions & 14 deletions crates/viewer/re_space_view_graph/src/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
//! Provides a basic abstraction over a graph that was logged to an entity.

// For now this is pretty basic, but in the future we might replace this with
// something like `petgraph`, which will unlock more user interactions, such as
// highlighting of neighboring nodes and edges.

mod hash;

use egui::{Pos2, Vec2};
pub(crate) use hash::GraphNodeHash;
mod index;
pub(crate) use index::NodeId;
mod ids;
pub(crate) use ids::{EdgeId, NodeId};

use re_chunk::EntityPath;
use re_types::components::{self, GraphType};

use crate::{
layout::EdgeTemplate,
ui::DrawableLabel,
visualizers::{EdgeData, NodeData, NodeInstance},
};
Expand Down Expand Up @@ -70,16 +77,10 @@ impl Node {
}
}

pub struct Edge {
pub from: NodeId,
pub to: NodeId,
pub arrow: bool,
}

pub struct Graph {
entity: EntityPath,
nodes: Vec<Node>,
edges: Vec<Edge>,
edges: Vec<EdgeTemplate>,
kind: GraphType,
}

Expand Down Expand Up @@ -127,10 +128,10 @@ impl Graph {
}
}

let es = data.edges.iter().map(|e| Edge {
from: e.source_index,
to: e.target_index,
arrow: data.graph_type == GraphType::Directed,
let es = data.edges.iter().map(|e| EdgeTemplate {
source: e.source_index,
target: e.target_index,
target_arrow: data.graph_type == GraphType::Directed,
});

(es.collect(), data.graph_type)
Expand All @@ -150,7 +151,7 @@ impl Graph {
&self.nodes
}

pub fn edges(&self) -> &[Edge] {
pub fn edges(&self) -> &[EdgeTemplate] {
&self.edges
}

Expand Down
76 changes: 76 additions & 0 deletions crates/viewer/re_space_view_graph/src/layout/geometry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Provides geometric (shape) abstractions for the different elements of a graph layout.

use egui::{Pos2, Rect, Vec2};

#[derive(Clone, Debug)]
pub enum PathGeometry {
/// A simple straight edge.
Line { source: Pos2, target: Pos2 },

/// Represents a cubic bezier curve.
///
/// In the future we could probably support more complex splines.
CubicBezier {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: we already depends on kurbo. I have a good experience using this crate for geometric primitives. Might be worth using it for some computations (e.g. bbox, linear approximation, etc.) if the need increases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, I heard good things about kurbo too!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been using it lots in my personal projects.

source: Pos2,
target: Pos2,
control: [Pos2; 2],
},
// We could add other geometries, such as `Orthogonal` here too.
}

#[derive(Debug)]
pub struct EdgeGeometry {
pub target_arrow: bool,
pub path: PathGeometry,
}

impl EdgeGeometry {
pub fn bounding_rect(&self) -> Rect {
match self.path {
PathGeometry::Line { source, target } => Rect::from_two_pos(source, target),
// TODO(grtlr): This is just a crude (upper) approximation, as the resulting bounding box can be too large.
// For now this is fine, as there are no interactions on edges yet.
PathGeometry::CubicBezier {
source,
target,
ref control,
} => Rect::from_points(&[&[source, target], control.as_slice()].concat()),
}
}

/// The starting position of an edge.
pub fn source_pos(&self) -> Pos2 {
match self.path {
PathGeometry::Line { source, .. } | PathGeometry::CubicBezier { source, .. } => source,
}
}

/// The end position of an edge.
pub fn target_pos(&self) -> Pos2 {
match self.path {
PathGeometry::Line { target, .. } | PathGeometry::CubicBezier { target, .. } => target,
}
}

/// The direction of the edge at the source node (normalized).
pub fn source_arrow_direction(&self) -> Vec2 {
use PathGeometry::{CubicBezier, Line};
match self.path {
Line { source, target } => (source.to_vec2() - target.to_vec2()).normalized(),
CubicBezier {
source, control, ..
} => (control[0].to_vec2() - source.to_vec2()).normalized(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. kurbo could provide the actual tangent here

}
}

/// The direction of the edge at the target node (normalized).
pub fn target_arrow_direction(&self) -> Vec2 {
use PathGeometry::{CubicBezier, Line};
match self.path {
Line { source, target } => (target.to_vec2() - source.to_vec2()).normalized(),
CubicBezier {
target, control, ..
} => (target.to_vec2() - control[1].to_vec2()).normalized(),
}
}
}
5 changes: 4 additions & 1 deletion crates/viewer/re_space_view_graph/src/layout/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
mod geometry;
mod provider;
mod request;
mod result;
mod slots;

pub use geometry::{EdgeGeometry, PathGeometry};
pub use provider::ForceLayoutProvider;
pub use request::LayoutRequest;
pub use request::{EdgeTemplate, LayoutRequest};
pub use result::Layout;
Loading
Loading