Skip to content

Commit

Permalink
[skip ci] Introduce new LayoutRequest to get rid of Discriminator
Browse files Browse the repository at this point in the history
  • Loading branch information
grtlr committed Nov 27, 2024
1 parent 22b5489 commit 2a1535c
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 144 deletions.
2 changes: 1 addition & 1 deletion crates/viewer/re_space_view_graph/src/graph/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use re_types::components;
use super::GraphNodeHash;

// TODO(grtlr): Rename this to `NodeId`.
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeIndex {
pub entity_hash: EntityPathHash,
pub node_hash: GraphNodeHash,
Expand Down
26 changes: 21 additions & 5 deletions crates/viewer/re_space_view_graph/src/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod hash;
use std::hash::{Hash as _, Hasher as _};

use egui::Pos2;
use egui::{Pos2, Vec2};
pub(crate) use hash::GraphNodeHash;
mod index;
pub(crate) use index::NodeIndex;
Expand Down Expand Up @@ -33,15 +33,26 @@ pub enum Node {
impl Node {
pub fn id(&self) -> NodeIndex {
match self {
Node::Explicit { id, .. } => *id,
Node::Implicit { id, .. } => *id,
Self::Explicit { id, .. } | Self::Implicit { id, .. } => *id,
}
}

pub fn label(&self) -> &DrawableLabel {
match self {
Node::Explicit { label, .. } => label,
Node::Implicit { label, .. } => label,
Self::Explicit { label, .. } | Self::Implicit { label, .. } => label,
}
}

pub fn size(&self) -> Vec2 {
match self {
Self::Explicit { label, .. } | Self::Implicit { label, .. } => label.size(),
}
}

pub fn position(&self) -> Option<Pos2> {
match self {
Self::Explicit { position, .. } => *position,
Self::Implicit { .. } => None,
}
}
}
Expand Down Expand Up @@ -136,6 +147,11 @@ impl Graph {
self.kind
}

pub fn entity(&self) -> &EntityPath {
&self.entity
}

#[deprecated]
pub fn size_hash(&self) -> u64 {
let mut hasher = ahash::AHasher::default();
for node in &self.nodes {
Expand Down
43 changes: 43 additions & 0 deletions crates/viewer/re_space_view_graph/src/layout/layout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use egui::{Pos2, Rect};

use crate::{graph::NodeIndex, ui::bounding_rect_from_iter};

pub type LineSegment = [Pos2; 2];

#[derive(Debug, PartialEq, Eq)]
pub struct Layout {
pub(super) nodes: ahash::HashMap<NodeIndex, Rect>,
pub(super) edges: ahash::HashMap<(NodeIndex, NodeIndex), LineSegment>,
}

impl Layout {
pub fn bounding_rect(&self) -> Rect {
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()
}

/// 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
}
}
132 changes: 6 additions & 126 deletions crates/viewer/re_space_view_graph/src/layout/mod.rs
Original file line number Diff line number Diff line change
@@ -1,127 +1,7 @@
use egui::{Pos2, Rect, Vec2};
use fjadra as fj;
mod layout;
mod provider;
mod request;

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

#[derive(Debug, PartialEq, Eq)]
pub struct Layout {
extents: ahash::HashMap<NodeIndex, Rect>,
}

impl Layout {
pub fn bounding_rect(&self) -> Rect {
bounding_rect_from_iter(self.extents.values().copied())
}

/// Gets the position and size of a node in the layout.
pub fn get(&self, node: &NodeIndex) -> Option<Rect> {
self.extents.get(node).copied()
}

/// Updates the size and position of a node, for example after size changes.
/// Returns `true` if the node changed its size.
pub fn update(&mut self, node: &NodeIndex, rect: Rect) -> bool {
debug_assert!(
self.extents.contains_key(node),
"node should exist in the layout"
);
if let Some(extent) = self.extents.get_mut(node) {
let size_changed = (extent.size() - rect.size()).length_sq() > 0.01;
*extent = rect;
return size_changed;
}
false
}
}

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),
_ => Self::default(),
}
}
}

pub struct ForceLayout {
simulation: fj::Simulation,
node_index: ahash::HashMap<NodeIndex, usize>,
}

impl ForceLayout {
pub fn new(graphs: &[Graph]) -> Self {
let nodes = graphs
.iter()
.flat_map(|g| g.nodes().iter().map(|n| (n.id(), fj::Node::from(n))));

let mut node_index = ahash::HashMap::default();
let all_nodes: Vec<fj::Node> = nodes
.enumerate()
.map(|(i, n)| {
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]))
});

// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints.
let simulation = fj::SimulationBuilder::default()
.with_alpha_decay(0.01) // TODO(grtlr): slows down the simulation for demo
.build(all_nodes)
.add_force(
"link",
fj::Link::new(all_edges).distance(50.0).iterations(2),
)
.add_force("charge", fj::ManyBody::new())
// TODO(grtlr): This is a small stop-gap until we have blueprints to prevent nodes from flying away.
.add_force("x", fj::PositionX::new().strength(0.01))
.add_force("y", fj::PositionY::new().strength(0.01));

Self {
simulation,
node_index,
}
}

pub fn init(&self) -> 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);
}

Layout { extents }
}

/// Returns `true` if finished.
pub fn tick(&mut self, layout: &mut Layout) -> bool {
self.simulation.tick(1);

let positions = self.simulation.positions().collect::<Vec<_>>();

for (node, extent) in &mut layout.extents {
let i = self.node_index[node];
let [x, y] = positions[i];
let pos = Pos2::new(x as f32, y as f32);
extent.set_center(pos);
}

self.simulation.finished()
}
}
pub use layout::Layout;
pub use provider::ForceLayoutProvider;
pub use request::LayoutRequest;
95 changes: 95 additions & 0 deletions crates/viewer/re_space_view_graph/src/layout/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use egui::{Pos2, Rect, Vec2};
use fjadra as fj;

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

use super::Layout;

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),
_ => Self::default(),
}
}
}

pub struct ForceLayoutProvider {
simulation: fj::Simulation,
node_index: ahash::HashMap<NodeIndex, usize>,
}

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))));

let mut node_index = ahash::HashMap::default();
let all_nodes: Vec<fj::Node> = nodes
.enumerate()
.map(|(i, n)| {
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]))
});

// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints.
let simulation = fj::SimulationBuilder::default()
.with_alpha_decay(0.01) // TODO(grtlr): slows down the simulation for demo
.build(all_nodes)
.add_force(
"link",
fj::Link::new(all_edges).distance(50.0).iterations(2),
)
.add_force("charge", fj::ManyBody::new())
// TODO(grtlr): This is a small stop-gap until we have blueprints to prevent nodes from flying away.
.add_force("x", fj::PositionX::new().strength(0.01))
.add_force("y", fj::PositionY::new().strength(0.01));

Self {
simulation,
node_index,
}
}

pub fn init(&self) -> 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);
}

Layout { nodes: extents, edges: ahash::HashMap::default() }
}

/// Returns `true` if finished.
pub fn tick(&mut self, layout: &mut Layout) -> bool {
self.simulation.tick(1);

let positions = self.simulation.positions().collect::<Vec<_>>();

for (node, extent) in &mut layout.nodes {
let i = self.node_index[node];
let [x, y] = positions[i];
let pos = Pos2::new(x as f32, y as f32);
extent.set_center(pos);
}

self.simulation.finished()
}
}
49 changes: 49 additions & 0 deletions crates/viewer/re_space_view_graph/src/layout/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::collections::{BTreeMap, BTreeSet};

use egui::{Pos2, Vec2};
use re_chunk::EntityPath;

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

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

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

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

impl LayoutRequest {
pub fn from_graphs<'a>(graphs: impl IntoIterator<Item = &'a Graph>) -> Self {
let mut request = Self {
graphs: BTreeMap::new(),
};

for graph in graphs {
let entity = request.graphs.entry(graph.entity().clone()).or_default();

for node in graph.nodes() {
let shape = NodeTemplate {
size: node.size(),
fixed_position: node.position(),
};
entity.nodes.insert(node.id(), shape);
}

for edge in graph.edges() {
entity.edges.insert((edge.from, edge.to));
}
}

request
}
}
Loading

0 comments on commit 2a1535c

Please sign in to comment.