1#![doc = include_str!("../README.md")]
2
3use std::error::Error;
4
5use fdg_sim::{
6 glam::Vec3,
7 petgraph::{
8 visit::{EdgeRef, IntoEdgeReferences},
9 EdgeType, Undirected,
10 },
11 Dimensions, ForceGraph, Simulation, SimulationParameters,
12};
13use plotters::prelude::*;
14
15pub use plotters::style;
16
17#[cfg(feature = "wasm")]
18mod wasm;
19
20#[cfg(feature = "wasm")]
21pub use wasm::*;
22
23pub struct Settings<N, E, Ty = Undirected> {
25 pub sim_parameters: SimulationParameters<N, E, Ty>,
27 pub iterations: usize,
29 pub dt: f32,
31 pub node_size: u32,
33 pub node_color: RGBAColor,
35 pub edge_size: u32,
37 pub edge_color: RGBAColor,
39 pub background_color: RGBAColor,
41 pub print_progress: bool,
43 pub text_style: Option<TextStyle<'static>>,
45}
46
47impl<N, E, Ty: EdgeType> Default for Settings<N, E, Ty> {
48 fn default() -> Self {
49 Self {
50 sim_parameters: SimulationParameters::default(),
51 iterations: 2000,
52 dt: 0.035,
53 node_size: 10,
54 node_color: RGBAColor(0, 0, 0, 1.0),
55 edge_size: 3,
56 edge_color: RGBAColor(255, 0, 0, 1.0),
57 background_color: RGBAColor(255, 255, 255, 1.0),
58 print_progress: false,
59 text_style: None,
60 }
61 }
62}
63
64pub fn gen_image<N, E, Ty: EdgeType>(
66 graph: ForceGraph<N, E, Ty>,
67 settings: Option<Settings<N, E, Ty>>,
68) -> Result<String, Box<dyn Error>> {
69 let settings = settings.unwrap_or_default();
71 let mut sim = Simulation::from_graph(graph, settings.sim_parameters);
72 sim.parameters_mut().dimensions = Dimensions::Two;
73
74 for i in 0..settings.iterations {
76 if settings.print_progress && i % 10 == 0 {
77 println!("{}/{}", i, settings.iterations);
78 }
79 sim.update(settings.dt);
80 }
81
82 let (graph_x, graph_y): (f32, f32) = {
84 let mut top = 0.0;
85 let mut bottom = 0.0;
86 let mut left = 0.0;
87 let mut right = 0.0;
88
89 for node in sim.get_graph().node_weights() {
90 let loc = node.location;
91
92 let rightmost = match settings.text_style.clone() {
94 Some(ts) => {
95 loc.x
96 + ts.font
97 .box_size(&node.name)
98 .ok()
99 .map(|x| x.0 as f32)
100 .unwrap_or(0.0)
101 }
102 None => loc.x,
103 };
104
105 if rightmost > right {
106 right = rightmost;
107 }
108
109 if loc.x < left {
110 left = loc.x;
111 }
112
113 if loc.y > top {
114 top = loc.y
115 }
116
117 if loc.y < bottom {
118 bottom = loc.y;
119 }
120 }
121
122 (
123 ((right + settings.node_size as f32) - (left - settings.node_size as f32)),
124 ((top + settings.node_size as f32) - (bottom - settings.node_size as f32)),
125 )
126 };
127
128 let image_scale = 1.5;
129 let (image_x, image_y) = (
130 (graph_x * image_scale) as u32,
131 (graph_y * image_scale) as u32,
132 );
133
134 let mut location_sum = Vec3::ZERO;
136 for node in sim.get_graph().node_weights() {
137 location_sum += node.location;
138 }
139
140 let avg_vec = location_sum / sim.get_graph().node_count() as f32;
141 for node in sim.get_graph_mut().node_weights_mut() {
142 node.location -= avg_vec;
143 }
144
145 for node in sim.get_graph_mut().node_weights_mut() {
147 node.location.x += (image_x / 2) as f32;
148 node.location.y += (image_y / 2) as f32;
149 }
150
151 let mut buffer = String::new();
153
154 let backend = SVGBackend::with_string(&mut buffer, (image_x, image_y)).into_drawing_area();
156
157 backend.fill(&settings.background_color).unwrap();
159
160 for edge in sim.get_graph().edge_references() {
162 let source = &sim.get_graph()[edge.source()].location;
163 let target = &sim.get_graph()[edge.target()].location;
164
165 backend.draw(&PathElement::new(
166 vec![
167 (source.x as i32, source.y as i32),
168 (target.x as i32, target.y as i32),
169 ],
170 ShapeStyle {
171 color: settings.edge_color,
172 filled: true,
173 stroke_width: settings.edge_size,
174 },
175 ))?;
176 }
177
178 for node in sim.get_graph().node_weights() {
180 backend.draw(&Circle::new(
181 (node.location.x as i32, node.location.y as i32),
182 settings.node_size,
183 ShapeStyle {
184 color: settings.node_color,
185 filled: true,
186 stroke_width: 1,
187 },
188 ))?;
189 }
190
191 if let Some(text_style) = settings.text_style {
193 for node in sim.get_graph().node_weights() {
194 let pos = (
195 node.location.x as i32 + (text_style.font.get_size() / 2.0) as i32,
196 node.location.y as i32,
197 );
198 backend.draw_text(node.name.as_str(), &text_style, pos)?;
199 }
200 }
201
202 drop(backend);
203
204 Ok(buffer)
205}