Skip to content

Commit

Permalink
Add support for Bezier-curve multi (self-)edges (#8256)
Browse files Browse the repository at this point in the history
### Related

<!--
Include links to any related issues/PRs in a bulleted list, for example:
* Closes #1234
* Part of #1337
-->
* Closes: #8238


### What

This PR brings self-edges in the form of Bezier-curves.

<img width="230" alt="image"
src="https://github.com/user-attachments/assets/85304534-891b-415d-ab8e-3006afe73058">

It also tries to make multiple edges between the same node more legible
by applying a small arc on such edges to prevent overlap.

<!--
Make sure the PR title and labels are set to maximize their usefulness
for the CHANGELOG,
and our `git log`.

If you have noticed any breaking changes, include them in the migration
guide.

We track various metrics at <https://build.rerun.io>.

For maintainers:
* To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
* To deploy documentation changes immediately after merging this PR, add
the `deploy docs` label.
-->
  • Loading branch information
grtlr authored Dec 3, 2024
1 parent 312f91c commit 0e9d221
Show file tree
Hide file tree
Showing 14 changed files with 570 additions and 223 deletions.
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 {
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(),
}
}

/// 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

0 comments on commit 0e9d221

Please sign in to comment.