Skip to content

Commit

Permalink
Basic edge drawing working
Browse files Browse the repository at this point in the history
  • Loading branch information
grtlr committed Nov 27, 2024
1 parent 2a1535c commit 8d9e1fb
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 194 deletions.
37 changes: 36 additions & 1 deletion crates/viewer/re_space_view_graph/src/canvas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use egui::{
emath::TSTransform, style::Interaction, Area, Color32, Id, Order, Pos2, Rect, Response, Sense, Stroke, Ui, UiBuilder, UiKind, Vec2
emath::TSTransform, style::Interaction, Area, Color32, Id, Order, Painter, Pos2, Rect,
Response, Sense, Shape, Stroke, Ui, UiBuilder, UiKind, Vec2,
};
use re_viewer_context::InteractionHighlight;

Expand Down Expand Up @@ -84,6 +85,40 @@ pub fn draw_debug(ui: &mut Ui, world_bounding_rect: Rect) {
}
}

/// Helper function to draw an arrow at the end of the edge
fn draw_arrow(painter: &Painter, tip: Pos2, direction: Vec2, color: Color32) {
let arrow_size = 10.0; // Adjust size as needed
let perpendicular = Vec2::new(-direction.y, direction.x) * 0.5 * arrow_size;

let p1 = tip - direction * arrow_size + perpendicular;
let p2 = tip - direction * arrow_size - perpendicular;

// Draw a filled triangle for the arrow
painter.add(Shape::convex_polygon(
vec![tip, p1, p2],
color,
Stroke::NONE,
));
}

pub fn draw_edge(ui: &mut Ui, points: [Pos2; 2], show_arrow: bool) -> Response {
let fg = ui.style().visuals.text_color();

let painter = ui.painter();

painter.line_segment(points, Stroke::new(1.0, fg));

// Calculate direction vector from source to target
let direction = (points[1] - points[0]).normalized();

// Conditionally draw an arrow at the target point
if show_arrow {
draw_arrow(painter, points[1], direction, fg);
}

ui.allocate_rect(Rect::from_points(&points), Sense::hover())
}

pub fn zoom_pan_area(
ui: &mut Ui,
view_rect: Rect,
Expand Down
4 changes: 2 additions & 2 deletions crates/viewer/re_space_view_graph/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl Node {
pub struct Edge {
pub from: NodeIndex,
pub to: NodeIndex,
marker: bool,
pub arrow: bool,
}

pub struct Graph {
Expand Down Expand Up @@ -119,7 +119,7 @@ impl Graph {
let es = data.edges.iter().map(|e| Edge {
from: e.source_index,
to: e.target_index,
marker: data.graph_type == GraphType::Directed,
arrow: data.graph_type == GraphType::Directed,
});

(es.collect(), data.graph_type)
Expand Down
33 changes: 13 additions & 20 deletions crates/viewer/re_space_view_graph/src/layout/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,25 @@ pub struct Layout {

impl Layout {
pub fn bounding_rect(&self) -> Rect {
// TODO(grtlr): We mostly use this for debugging, but we should probably
// take all elements of the layout into account.
bounding_rect_from_iter(self.nodes.values().copied())
}

/// Gets the final position and size of a node in the layout.
pub fn get_node(&self, node: &NodeIndex) -> Option<Rect> {
self.nodes.get(node).copied()
///
/// Returns `Rect::ZERO` if the node is not present in the layout.
pub fn get_node(&self, node: &NodeIndex) -> Rect {
self.nodes.get(node).copied().unwrap_or(Rect::ZERO)
}

/// Gets the shape of an edge in the final layout.
pub fn get_edge(&self, from: NodeIndex, to: NodeIndex) -> Option<LineSegment> {
self.edges.get(&(from, to)).copied()
}

/// Updates the size and position of a node, for example after size changes.
/// Returns `true` if the node changed its size.
#[deprecated(note = "We should not need to update sizes anymore.")]
pub fn update(&mut self, node: &NodeIndex, rect: Rect) -> bool {
debug_assert!(
self.nodes.contains_key(node),
"node should exist in the layout"
);
if let Some(extent) = self.nodes.get_mut(node) {
let size_changed = (extent.size() - rect.size()).length_sq() > 0.01;
*extent = rect;
return size_changed;
}
false
///
/// Returns `[Pos2::ZERO, Pos2::ZERO]` if the edge is not present in the layout.
pub fn get_edge(&self, from: NodeIndex, to: NodeIndex) -> LineSegment {
self.edges
.get(&(from, to))
.copied()
.unwrap_or([Pos2::ZERO, Pos2::ZERO])
}
}
108 changes: 81 additions & 27 deletions crates/viewer/re_space_view_graph/src/layout/provider.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use egui::{Pos2, Rect, Vec2};
use fjadra as fj;

use crate::graph::{Graph, Node, NodeIndex};
use crate::graph::NodeIndex;

use super::Layout;
use super::{request::NodeTemplate, Layout, LayoutRequest};

impl<'a> From<&'a Node> for fj::Node {
fn from(node: &'a Node) -> Self {
match node {
Node::Explicit {
position: Some(pos),
..
} => Self::default().fixed_position(pos.x as f64, pos.y as f64),
impl<'a> From<&'a NodeTemplate> for fj::Node {
fn from(node: &'a NodeTemplate) -> Self {
match node.fixed_position {
Some(pos) => Self::default().fixed_position(pos.x as f64, pos.y as f64),
_ => Self::default(),
}
}
Expand All @@ -20,28 +17,35 @@ impl<'a> From<&'a Node> for fj::Node {
pub struct ForceLayoutProvider {
simulation: fj::Simulation,
node_index: ahash::HashMap<NodeIndex, usize>,
edges: Vec<(NodeIndex, NodeIndex)>,
}

impl ForceLayoutProvider {
pub fn new(graphs: &[Graph]) -> Self {
let nodes = graphs
.iter()
.flat_map(|g| g.nodes().iter().map(|n| (n.id(), fj::Node::from(n))));
pub fn new(request: &LayoutRequest) -> Self {
let nodes = request.graphs.iter().flat_map(|(_, graph_template)| {
graph_template
.nodes
.iter()
.map(|n| (n.0, fj::Node::from(n.1)))
});

let mut node_index = ahash::HashMap::default();
let all_nodes: Vec<fj::Node> = nodes
.enumerate()
.map(|(i, n)| {
node_index.insert(n.0, i);
node_index.insert(*n.0, i);
n.1
})
.collect();

let all_edges = graphs.iter().flat_map(|g| {
g.edges()
.iter()
.map(|e| (node_index[&e.from], node_index[&e.to]))
});
let all_edges_iter = request
.graphs
.iter()
.flat_map(|(_, graph_template)| graph_template.edges.iter());

let all_edges = all_edges_iter
.clone()
.map(|(a, b)| (node_index[&a], node_index[&b]));

// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints.
let simulation = fj::SimulationBuilder::default()
Expand All @@ -59,22 +63,28 @@ impl ForceLayoutProvider {
Self {
simulation,
node_index,
edges: all_edges_iter.cloned().collect(),
}
}

pub fn init(&self) -> Layout {
pub fn init(&self, request: &LayoutRequest) -> Layout {
let positions = self.simulation.positions().collect::<Vec<_>>();
let mut extents = ahash::HashMap::default();

for (node, i) in &self.node_index {
let [x, y] = positions[*i];
let pos = Pos2::new(x as f32, y as f32);
let size = Vec2::ZERO;
let rect = Rect::from_min_size(pos, size);
extents.insert(*node, rect);
for graph in request.graphs.values() {
for (id, node) in &graph.nodes {
let i = self.node_index[&id];
let [x, y] = positions[i];
let pos = Pos2::new(x as f32, y as f32);
extents.insert(*id, Rect::from_center_size(pos, node.size));
}
}

Layout { nodes: extents, edges: ahash::HashMap::default() }
Layout {
nodes: extents,
// Without any real node positions, we probably don't want to draw edges either.
edges: ahash::HashMap::default(),
}
}

/// Returns `true` if finished.
Expand All @@ -90,6 +100,50 @@ impl ForceLayoutProvider {
extent.set_center(pos);
}

for (from, to) in &self.edges {
layout.edges.insert(
(*from, *to),
line_segment(layout.nodes[from], layout.nodes[to]),
);
}

self.simulation.finished()
}
}

/// Helper function to calculate the line segment between two rectangles.
fn line_segment(source: Rect, target: Rect) -> [Pos2; 2] {
let source_center = source.center();
let target_center = target.center();

// Calculate direction vector from source to target
let direction = (target_center - source_center).normalized();

// Find the border points on both rectangles
let source_point = find_border_point(source, -direction); // Reverse direction for target
let target_point = find_border_point(target, direction);

[source_point, target_point]
}

/// Helper function to find the point where the line intersects the border of a rectangle
fn find_border_point(rect: Rect, direction: Vec2) -> Pos2 {
let mut t_min = f32::NEG_INFINITY;
let mut t_max = f32::INFINITY;

for i in 0..2 {
let inv_d = 1.0 / direction[i];
let mut t0 = (rect.min[i] - rect.center()[i]) * inv_d;
let mut t1 = (rect.max[i] - rect.center()[i]) * inv_d;

if inv_d < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}

t_min = t_min.max(t0);
t_max = t_max.min(t1);
}

let t = t_max.min(t_min); // Pick the first intersection
rect.center() + t * direction
}
14 changes: 7 additions & 7 deletions crates/viewer/re_space_view_graph/src/layout/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ use re_chunk::EntityPath;
use crate::graph::{Graph, NodeIndex};

#[derive(PartialEq)]
struct NodeTemplate {
size: Vec2,
fixed_position: Option<Pos2>,
pub(super) struct NodeTemplate {
pub(super) size: Vec2,
pub(super) fixed_position: Option<Pos2>,
}

#[derive(Default, PartialEq)]
struct GraphTemplate {
nodes: BTreeMap<NodeIndex, NodeTemplate>,
edges: BTreeSet<(NodeIndex, NodeIndex)>,
pub(super) struct GraphTemplate {
pub(super) nodes: BTreeMap<NodeIndex, NodeTemplate>,
pub(super) edges: BTreeSet<(NodeIndex, NodeIndex)>,
}

#[derive(PartialEq)]
pub struct LayoutRequest {
graphs: BTreeMap<EntityPath, GraphTemplate>,
pub(super) graphs: BTreeMap<EntityPath, GraphTemplate>,
}

impl LayoutRequest {
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_space_view_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
//!
//! A Space View that shows a graph (node-link diagram).
mod canvas;
mod graph;
mod layout;
mod properties;
mod ui;
mod view;
mod visualizers;
mod canvas;

pub use view::GraphSpaceView;
67 changes: 0 additions & 67 deletions crates/viewer/re_space_view_graph/src/ui/draw/edge.rs

This file was deleted.

4 changes: 1 addition & 3 deletions crates/viewer/re_space_view_graph/src/ui/draw/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
mod edge;
mod entity;
mod node;

pub use edge::draw_edge;
pub use entity::draw_entity;
pub use node::{DrawableLabel};
pub use node::DrawableLabel;
Loading

0 comments on commit 8d9e1fb

Please sign in to comment.