Skip to content

Commit

Permalink
WIP: layout switching and convenience
Browse files Browse the repository at this point in the history
  • Loading branch information
grtlr committed Oct 10, 2024
1 parent 5db85dd commit a16219f
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 86 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2697,6 +2697,7 @@ version = "0.0.0"
dependencies = [
"bytemuck",
"fdg-sim",
"layout-rs",
"mimalloc",
"petgraph",
"re_crash_handler",
Expand Down Expand Up @@ -3173,6 +3174,12 @@ dependencies = [
"libc",
]

[[package]]
name = "layout-rs"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84deb28a3a6c839ca42a7341664f32281416d69e2f29deb85aec5cc0243fdea8"

[[package]]
name = "lazy_static"
version = "1.4.0"
Expand Down
5 changes: 4 additions & 1 deletion examples/rust/graph_view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ mimalloc = "0.1"

petgraph = "0.6"
bytemuck = "1.18"
fdg-sim = "0.9"
thiserror = "1.0"

# Experiment with different layout algorithms.
fdg-sim = "0.9"
layout-rs = "0.1"
122 changes: 122 additions & 0 deletions examples/rust/graph_view/src/layout/dot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::collections::HashMap;

use layout::{
core::{
base::Orientation,
format::{ClipHandle, RenderBackend},
geometry::Point,
style::StyleAttr,
},
std_shapes::shapes::{Arrow, Element, ShapeKind},
topo::layout::VisualGraph,
};
use re_viewer::external::egui;

use crate::{error::Error, types::NodeIndex};

use super::Layout;

#[derive(Debug, Default, PartialEq, Eq)]
pub struct DotLayout;

impl Layout for DotLayout {
type NodeIx = NodeIndex;

fn compute(
&self,
nodes: impl IntoIterator<Item = (Self::NodeIx, egui::Vec2)>,
directed: impl IntoIterator<Item = (Self::NodeIx, Self::NodeIx)>,
undirected: impl IntoIterator<Item = (Self::NodeIx, Self::NodeIx)>,
) -> Result<HashMap<Self::NodeIx, egui::Rect>, Error> {
let mut handle_to_ix = HashMap::new();
let mut ix_to_handle = HashMap::new();

let mut graph = VisualGraph::new(Orientation::TopToBottom);

for (ix, size) in nodes {
let size = Point::new(size.x as f64, size.y as f64);
let handle = graph.add_node(Element::create(
ShapeKind::new_box("test"),
StyleAttr::simple(),
Orientation::LeftToRight,
size,
));
handle_to_ix.insert(handle, ix.clone());
ix_to_handle.insert(ix, handle);
}

for (source_ix, target_ix) in directed {
let source = ix_to_handle
.get(&source_ix)
.ok_or_else(|| Error::EdgeUnknownNode(source_ix.to_string()))?;
let target = ix_to_handle
.get(&target_ix)
.ok_or_else(|| Error::EdgeUnknownNode(target_ix.to_string()))?;
graph.add_edge(Arrow::simple("test"), *source, *target);
}

for (source_ix, target_ix) in undirected {
let source = ix_to_handle
.get(&source_ix)
.ok_or_else(|| Error::EdgeUnknownNode(source_ix.to_string()))?;
let target = ix_to_handle
.get(&target_ix)
.ok_or_else(|| Error::EdgeUnknownNode(target_ix.to_string()))?;

// TODO(grtlr): find a better way other than adding duplicate edges.
graph.add_edge(Arrow::simple("test"), *source, *target);
graph.add_edge(Arrow::simple("test"), *target, *source);
}

graph.do_it(false, false, false, &mut DummyBackend);

let res = handle_to_ix
.into_iter()
.map(|(h, ix)| {
let (min, max) = graph.pos(h).bbox(false);
(
ix,
egui::Rect::from_min_max(
egui::Pos2::new(min.x as f32, min.y as f32),
egui::Pos2::new(max.x as f32, max.y as f32),
),
)
})
.collect();

Ok(res)
}
}

struct DummyBackend;

impl RenderBackend for DummyBackend {
fn draw_rect(
&mut self,
_xy: Point,
_size: Point,
_look: &StyleAttr,
_clip: Option<layout::core::format::ClipHandle>,
) {
}

fn draw_line(&mut self, _start: Point, _stop: Point, _look: &StyleAttr) {}

fn draw_circle(&mut self, _xy: Point, _size: Point, _look: &StyleAttr) {}

fn draw_text(&mut self, _xy: Point, _text: &str, _look: &StyleAttr) {}

fn draw_arrow(
&mut self,
_path: &[(Point, Point)],
_dashed: bool,
_head: (bool, bool),
_look: &StyleAttr,
_text: &str,
) {
}

fn create_clip(&mut self, _xy: Point, _size: Point, _rounded_px: usize) -> ClipHandle {
ClipHandle::default()
}
}
17 changes: 4 additions & 13 deletions examples/rust/graph_view/src/layout/force_directed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,14 @@ use re_viewer::external::egui;

use crate::{error::Error, types::NodeIndex};

use super::LayoutProvider;
use super::Layout;

#[derive(Debug, Default, PartialEq, Eq)]
pub struct ForceBasedLayout;

impl ForceBasedLayout {
pub fn new() -> Self {
Self
}
}

impl LayoutProvider for ForceBasedLayout {
impl Layout for ForceBasedLayout {
type NodeIx = NodeIndex;

fn name() -> &'static str {
"Force Directed"
}

fn compute(
&self,
nodes: impl IntoIterator<Item = (Self::NodeIx, egui::Vec2)>,
Expand All @@ -36,7 +27,7 @@ impl LayoutProvider for ForceBasedLayout {
node_to_index.insert(node_id, ix);
}

for (source, target) in directed.into_iter().chain(undirected).into_iter() {
for (source, target) in directed.into_iter().chain(undirected) {
let source_ix = node_to_index
.get(&source)
.ok_or_else(|| Error::EdgeUnknownNode(source.to_string()))?;
Expand Down
38 changes: 34 additions & 4 deletions examples/rust/graph_view/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,50 @@ use std::collections::HashMap;

use re_viewer::external::egui;

use crate::error::Error;
use crate::{error::Error, types::NodeIndex};

mod dot;
pub(crate) use dot::DotLayout;
mod force_directed;
pub(crate) use force_directed::ForceBasedLayout;

pub(crate) trait LayoutProvider {
pub(crate) trait Layout {
type NodeIx: Clone + Eq + std::hash::Hash;

fn name() -> &'static str;

fn compute(
&self,
nodes: impl IntoIterator<Item = (Self::NodeIx, egui::Vec2)>,
directed: impl IntoIterator<Item = (Self::NodeIx, Self::NodeIx)>,
undirected: impl IntoIterator<Item = (Self::NodeIx, Self::NodeIx)>,
) -> Result<HashMap<Self::NodeIx, egui::Rect>, Error>;
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum LayoutProvider {
Dot(DotLayout),
ForceDirected(ForceBasedLayout),
}

impl LayoutProvider {
pub(crate) fn new_dot() -> Self {
LayoutProvider::Dot(Default::default())
}

pub(crate) fn new_force_directed() -> Self {
LayoutProvider::ForceDirected(Default::default())
}
}

impl LayoutProvider {
pub(crate) fn compute(
&self,
nodes: impl IntoIterator<Item = (NodeIndex, egui::Vec2)>,
directed: impl IntoIterator<Item = (NodeIndex, NodeIndex)>,
undirected: impl IntoIterator<Item = (NodeIndex, NodeIndex)>,
) -> Result<HashMap<NodeIndex, egui::Rect>, Error> {
match self {
LayoutProvider::Dot(layout) => layout.compute(nodes, directed, undirected),
LayoutProvider::ForceDirected(layout) => layout.compute(nodes, directed, undirected),
}
}
}
69 changes: 3 additions & 66 deletions examples/rust/graph_view/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use re_viewer::external::{

mod edge;
pub(crate) use edge::draw_edge;
mod state;
pub(crate) use state::GraphSpaceViewState;

use crate::{graph::Node, types::{NodeIndex, NodeInstance, UnknownNodeInstance}};
use crate::{graph::Node, layout::LayoutProvider, types::{NodeIndex, NodeInstance, UnknownNodeInstance}};

pub fn draw_node(
ui: &mut egui::Ui,
Expand Down Expand Up @@ -128,20 +130,6 @@ pub fn measure_node_sizes<'a>(
sizes
}

/// Space view state for the custom space view.
///
/// This state is preserved between frames, but not across Viewer sessions.
#[derive(Default)]
pub(crate) struct GraphSpaceViewState {
pub world_to_view: emath::TSTransform,

// Debug information
pub show_debug: bool,

/// Positions of the nodes in world space.
pub layout: Option<HashMap<NodeIndex, egui::Rect>>,
}

pub fn bounding_rect_from_iter<'a>(
rectangles: impl Iterator<Item = &'a egui::Rect>,
) -> Option<egui::Rect> {
Expand All @@ -157,54 +145,3 @@ pub fn bounding_rect_from_iter<'a>(

bounding_rect
}

impl GraphSpaceViewState {
pub fn fit_to_screen(&mut self, bounding_rect: egui::Rect, available_size: egui::Vec2) {
// Compute the scale factor to fit the bounding rectangle into the available screen size.
let scale_x = available_size.x / bounding_rect.width();
let scale_y = available_size.y / bounding_rect.height();

// Use the smaller of the two scales to ensure the whole rectangle fits on the screen.
let scale = scale_x.min(scale_y).min(1.0);

// Compute the translation to center the bounding rect in the screen.
let center_screen = egui::Pos2::new(available_size.x / 2.0, available_size.y / 2.0);
let center_world = bounding_rect.center().to_vec2();

// Set the transformation to scale and then translate to center.
self.world_to_view =
emath::TSTransform::from_translation(center_screen.to_vec2() - center_world * scale)
* emath::TSTransform::from_scaling(scale);
}

pub fn bounding_box_ui(&mut self, ui: &mut egui::Ui) {
if let Some(layout) = &self.layout {
ui.grid_left_hand_label("Bounding box")
.on_hover_text("The bounding box encompassing all Entities in the view right now");
ui.vertical(|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if let Some(egui::Rect { min, max }) = bounding_rect_from_iter(layout.values()) {
ui.label(format!("x [{} - {}]", format_f32(min.x), format_f32(max.x),));
ui.label(format!("y [{} - {}]", format_f32(min.y), format_f32(max.y),));
}
});
ui.end_row();
}
}

pub fn debug_ui(&mut self, ui: &mut egui::Ui) {
ui.re_checkbox(&mut self.show_debug, "Show debug information")
.on_hover_text("Shows debug information for the current graph");
ui.end_row();
}
}

impl SpaceViewState for GraphSpaceViewState {
fn as_any(&self) -> &dyn std::any::Any {
self
}

fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
Loading

0 comments on commit a16219f

Please sign in to comment.