#![doc = include_str!("../README.md")]
use std::error::Error;
use fdg_sim::{
glam::Vec3,
petgraph::{
visit::{EdgeRef, IntoEdgeReferences},
EdgeType, Undirected,
},
Dimensions, ForceGraph, Simulation, SimulationParameters,
};
use plotters::prelude::*;
pub use plotters::style;
#[cfg(feature = "wasm")]
mod wasm;
#[cfg(feature = "wasm")]
pub use wasm::*;
pub struct Settings<N, E, Ty = Undirected> {
pub sim_parameters: SimulationParameters<N, E, Ty>,
pub iterations: usize,
pub dt: f32,
pub node_size: u32,
pub node_color: RGBAColor,
pub edge_size: u32,
pub edge_color: RGBAColor,
pub background_color: RGBAColor,
pub print_progress: bool,
pub text_style: Option<TextStyle<'static>>,
}
impl<N, E, Ty: EdgeType> Default for Settings<N, E, Ty> {
fn default() -> Self {
Self {
sim_parameters: SimulationParameters::default(),
iterations: 2000,
dt: 0.035,
node_size: 10,
node_color: RGBAColor(0, 0, 0, 1.0),
edge_size: 3,
edge_color: RGBAColor(255, 0, 0, 1.0),
background_color: RGBAColor(255, 255, 255, 1.0),
print_progress: false,
text_style: None,
}
}
}
pub fn gen_image<N, E, Ty: EdgeType>(
graph: ForceGraph<N, E, Ty>,
settings: Option<Settings<N, E, Ty>>,
) -> Result<String, Box<dyn Error>> {
let settings = settings.unwrap_or_default();
let mut sim = Simulation::from_graph(graph, settings.sim_parameters);
sim.parameters_mut().dimensions = Dimensions::Two;
for i in 0..settings.iterations {
if settings.print_progress && i % 10 == 0 {
println!("{}/{}", i, settings.iterations);
}
sim.update(settings.dt);
}
let (graph_x, graph_y): (f32, f32) = {
let mut top = 0.0;
let mut bottom = 0.0;
let mut left = 0.0;
let mut right = 0.0;
for node in sim.get_graph().node_weights() {
let loc = node.location;
let rightmost = match settings.text_style.clone() {
Some(ts) => {
loc.x
+ ts.font
.box_size(&node.name)
.ok()
.map(|x| x.0 as f32)
.unwrap_or(0.0)
}
None => loc.x,
};
if rightmost > right {
right = rightmost;
}
if loc.x < left {
left = loc.x;
}
if loc.y > top {
top = loc.y
}
if loc.y < bottom {
bottom = loc.y;
}
}
(
((right + settings.node_size as f32) - (left - settings.node_size as f32)),
((top + settings.node_size as f32) - (bottom - settings.node_size as f32)),
)
};
let image_scale = 1.5;
let (image_x, image_y) = (
(graph_x * image_scale) as u32,
(graph_y * image_scale) as u32,
);
let mut location_sum = Vec3::ZERO;
for node in sim.get_graph().node_weights() {
location_sum += node.location;
}
let avg_vec = location_sum / sim.get_graph().node_count() as f32;
for node in sim.get_graph_mut().node_weights_mut() {
node.location -= avg_vec;
}
for node in sim.get_graph_mut().node_weights_mut() {
node.location.x += (image_x / 2) as f32;
node.location.y += (image_y / 2) as f32;
}
let mut buffer = String::new();
let backend = SVGBackend::with_string(&mut buffer, (image_x, image_y)).into_drawing_area();
backend.fill(&settings.background_color).unwrap();
for edge in sim.get_graph().edge_references() {
let source = &sim.get_graph()[edge.source()].location;
let target = &sim.get_graph()[edge.target()].location;
backend.draw(&PathElement::new(
vec![
(source.x as i32, source.y as i32),
(target.x as i32, target.y as i32),
],
ShapeStyle {
color: settings.edge_color,
filled: true,
stroke_width: settings.edge_size,
},
))?;
}
for node in sim.get_graph().node_weights() {
backend.draw(&Circle::new(
(node.location.x as i32, node.location.y as i32),
settings.node_size,
ShapeStyle {
color: settings.node_color,
filled: true,
stroke_width: 1,
},
))?;
}
if let Some(text_style) = settings.text_style {
for node in sim.get_graph().node_weights() {
let pos = (
node.location.x as i32 + (text_style.font.get_size() / 2.0) as i32,
node.location.y as i32,
);
backend.draw_text(node.name.as_str(), &text_style, pos)?;
}
}
drop(backend);
Ok(buffer)
}