use crate::graph::SimTopology;
use crate::particle::Particle;
use super::force_directed::{ForceDirectedLayout, ForceParams};
use super::hierarchical::{HierarchicalLayout, HierarchicalParams};
use super::radial::{RadialLayout, RadialParams};
use super::{Layout, LayoutTickResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LayoutKind {
Force,
Hierarchical,
Radial,
}
pub struct GraphLayoutMode {
kind: LayoutKind,
force: ForceDirectedLayout,
hierarchical: HierarchicalLayout,
radial: RadialLayout,
paused: bool,
}
impl Default for GraphLayoutMode {
fn default() -> Self {
Self {
kind: LayoutKind::Force,
force: ForceDirectedLayout::default(),
hierarchical: HierarchicalLayout::default(),
radial: RadialLayout::default(),
paused: false,
}
}
}
impl GraphLayoutMode {
pub fn kind(&self) -> LayoutKind {
self.kind
}
pub fn set_kind(&mut self, kind: LayoutKind) {
self.kind = kind;
self.paused = false;
match kind {
LayoutKind::Force => self.force.reheat(1.0),
LayoutKind::Hierarchical => self.hierarchical.reheat(1.0),
LayoutKind::Radial => self.radial.reheat(1.0),
}
}
pub fn paused(&self) -> bool {
self.paused
}
pub fn set_paused(&mut self, paused: bool) {
self.paused = paused;
}
pub fn force_params(&self) -> &ForceParams {
self.force.params()
}
pub fn set_force_params(&mut self, params: ForceParams) {
self.force.set_params(params);
}
pub fn hierarchical_params(&self) -> &HierarchicalParams {
self.hierarchical.params()
}
pub fn set_hierarchical_params(&mut self, params: HierarchicalParams) {
self.hierarchical.set_params(params);
}
pub fn radial_params(&self) -> &RadialParams {
self.radial.params()
}
pub fn set_radial_params(&mut self, params: RadialParams) {
self.radial.set_params(params);
}
}
impl Layout for GraphLayoutMode {
fn tick(&mut self, topo: &SimTopology<'_>, particles: &mut [Particle], dt: f32) -> LayoutTickResult {
if self.paused {
return LayoutTickResult { alpha: 0.0, max_displacement: 0.0, settled: true };
}
match self.kind {
LayoutKind::Force => self.force.tick(topo, particles, dt),
LayoutKind::Hierarchical => self.hierarchical.tick(topo, particles, dt),
LayoutKind::Radial => self.radial.tick(topo, particles, dt),
}
}
fn reheat(&mut self, alpha: f32) {
match self.kind {
LayoutKind::Force => self.force.reheat(alpha),
LayoutKind::Hierarchical => self.hierarchical.reheat(alpha),
LayoutKind::Radial => self.radial.reheat(alpha),
}
}
fn is_settled(&self) -> bool {
if self.paused {
return true;
}
match self.kind {
LayoutKind::Force => self.force.is_settled(),
LayoutKind::Hierarchical => self.hierarchical.is_settled(),
LayoutKind::Radial => self.radial.is_settled(),
}
}
fn set_alpha_target(&mut self, target: f32) {
match self.kind {
LayoutKind::Force => self.force.set_alpha_target(target),
LayoutKind::Hierarchical => self.hierarchical.set_alpha_target(target),
LayoutKind::Radial => self.radial.set_alpha_target(target),
}
}
fn alpha_target(&self) -> f32 {
match self.kind {
LayoutKind::Force => self.force.alpha_target(),
LayoutKind::Hierarchical => self.hierarchical.alpha_target(),
LayoutKind::Radial => self.radial.alpha_target(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{NodeIndex, SimEdge};
fn topo(node_count: usize, edges: &[SimEdge]) -> SimTopology<'_> {
SimTopology { node_count, edges, degree: &[], radii: vec![1.0; node_count] }
}
#[test]
fn set_kind_switches_which_algorithm_tick_dispatches_to() {
let edges = [SimEdge { from: NodeIndex(0), to: NodeIndex(1), weight: 1.0 }];
let t = topo(2, &edges);
let mut particles = vec![Particle::default(); 2];
let mut mode = GraphLayoutMode::default();
assert_eq!(mode.kind(), LayoutKind::Force);
mode.set_kind(LayoutKind::Hierarchical);
assert_eq!(mode.kind(), LayoutKind::Hierarchical);
let r = mode.tick(&t, &mut particles, 1.0 / 60.0);
assert!(r.settled);
assert_eq!(particles[0].y, 0.0);
assert!(particles[1].y > particles[0].y);
}
#[test]
fn set_kind_clears_a_previous_pause() {
let mut mode = GraphLayoutMode::default();
mode.set_paused(true);
assert!(mode.paused());
mode.set_kind(LayoutKind::Hierarchical);
assert!(!mode.paused(), "switching kind must clear a stale pause from the previous kind");
}
#[test]
fn paused_layout_does_not_advance_particles_and_reports_settled() {
let edges: Vec<SimEdge> = Vec::new();
let t = topo(3, &edges);
let mut particles = vec![Particle::at(-10.0, 0.0), Particle::at(10.0, 0.0), Particle::at(0.0, 15.0)];
let mut mode = GraphLayoutMode::default();
mode.tick(&t, &mut particles, 1.0 / 60.0);
mode.tick(&t, &mut particles, 1.0 / 60.0);
let before = particles.clone();
mode.set_paused(true);
for _ in 0..10 {
let r = mode.tick(&t, &mut particles, 1.0 / 60.0);
assert!(r.settled, "a paused layout must report settled for redraw purposes");
assert_eq!(r.max_displacement, 0.0);
}
assert_eq!(particles, before, "a paused layout must not move any particle");
assert!(mode.is_settled());
mode.set_paused(false);
mode.tick(&t, &mut particles, 1.0 / 60.0);
assert_ne!(particles, before, "resuming must let the sim move particles again");
}
}