pub mod agent;
pub mod camera;
pub mod camera3d;
pub mod cluster;
pub mod engine;
pub mod engine3d;
pub mod graph;
pub mod interaction;
pub mod label_grid;
pub mod layout;
pub mod particle;
pub mod render;
pub mod render3d;
pub mod style;
pub mod theme;
pub use camera::{Aabb, Camera2D};
pub use camera3d::Camera3D;
pub use cluster::{AggregatedEdge, ClusterRegistry, ClusterState, GroupId};
pub use engine::{
DragEndPolicy, FilterSpec, GraphEngine, GraphInteractionConfig, GraphPointerBindings,
NodeFacts, SelectMode,
};
pub use engine3d::{GraphEngine3D, TransitionDirection};
pub use graph::{EdgeIndex, Graph, GraphEdge, GraphNode, NodeIndex, SimEdge, SimTopology};
pub use label_grid::LabelLodConfig;
pub use layout::{
ForceDirectedLayout, ForceDirectedLayout3D, ForceParams, ForceParams3D, GraphLayoutMode, GraphLayoutMode3D,
HierarchicalLayout, HierarchicalLayout3D, HierarchicalParams, HierarchicalParams3D, Layout, LayoutKind,
LayoutTickResult, RadialLayout, RadialLayout3D, RadialParams, RadialParams3D,
};
pub use particle::Particle;
pub use style::{DashPattern, EdgeVisualStyle, NodeMarker, NodeVisualStyle};
pub use theme::GraphTheme;
pub use uzor_figures::interact::FocusSet;
pub use uzor_figures::scale::color::CategoricalScale;
#[cfg(test)]
mod proof_tests {
use uzor::types::Rect;
use uzor_export::{render_to_png, ExportSpec};
use crate::engine::GraphEngine;
use crate::graph::{Graph, NodeIndex};
use crate::layout::force_directed::ForceDirectedLayout;
use crate::layout::hierarchical::{HierarchicalLayout, HierarchicalParams};
use crate::layout::radial::{RadialLayout, RadialParams};
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
fn export_spec() -> ExportSpec {
ExportSpec { width_px: WIDTH, height_px: HEIGHT, dpr: 1.0, background: Some([13, 15, 20, 255]) }
}
fn out_dir() -> std::path::PathBuf {
std::path::PathBuf::from(r"C:\Users\VA PC\CODING\ML_TRADING\nemo\uzor\out")
}
fn write_proof_png(name: &str, bytes: &[u8]) {
let dir = out_dir();
std::fs::create_dir_all(&dir).expect("create uzor/out/ proof directory");
std::fs::write(dir.join(name), bytes).expect("write proof PNG");
}
fn decoded_png_dims(bytes: &[u8]) -> (u32, u32) {
let decoder = png::Decoder::new(bytes);
let reader = decoder.read_info().expect("valid PNG header");
let info = reader.info();
(info.width, info.height)
}
type DemoGraph = Graph<(), ()>;
fn seeded_tree_fixture() -> DemoGraph {
let mut graph = DemoGraph::new();
for r in 0..3 {
let root = graph.push_node((), format!("root-{r}"), format!("cluster-{r}"), 7.0);
for c in 0..3 {
let mid = graph.push_node((), format!("r{r}-c{c}"), format!("cluster-{r}"), 5.0);
graph.push_edge(root, mid, 1.0, ());
for l in 0..2 {
let leaf = graph.push_node((), format!("r{r}-c{c}-l{l}"), format!("cluster-{r}"), 4.0);
graph.push_edge(mid, leaf, 1.0, ());
}
}
}
graph
}
fn seeded_cluster_fixture() -> (DemoGraph, [Vec<NodeIndex>; 3]) {
let mut graph = DemoGraph::new();
let hub = graph.push_node((), "hub", "hub", 5.0);
let mut clusters: [Vec<NodeIndex>; 3] = [Vec::new(), Vec::new(), Vec::new()];
for (c, cluster) in clusters.iter_mut().enumerate() {
let mut members = Vec::new();
for m in 0..8 {
let id = graph.push_node((), format!("c{c}n{m}"), format!("cluster-{c}"), 4.0);
members.push(id);
}
for i in 0..members.len() {
graph.push_edge(members[i], members[(i + 1) % members.len()], 1.0, ());
}
graph.push_edge(hub, members[0], 1.0, ());
graph.push_edge(members[1], hub, 0.6, ());
*cluster = members;
}
(graph, clusters)
}
#[test]
fn hierarchical_layout_renders_layered_graph_to_a_valid_png() {
let graph = seeded_tree_fixture();
let mut engine: GraphEngine<(), (), HierarchicalLayout> =
GraphEngine::new(graph, HierarchicalLayout::new(HierarchicalParams::default()));
engine.set_canvas_rect(Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64));
engine.tick(1.0 / 60.0);
engine.fit_view();
let bytes = render_to_png(&export_spec(), |ctx| {
engine.draw(ctx);
})
.expect("hierarchical graph should render");
assert_eq!(decoded_png_dims(&bytes), (WIDTH, HEIGHT));
write_proof_png("graph_hierarchical.png", &bytes);
}
#[test]
fn radial_layout_renders_ringed_graph_to_a_valid_png() {
let graph = seeded_tree_fixture();
let mut engine: GraphEngine<(), (), RadialLayout> = GraphEngine::new(graph, RadialLayout::new(RadialParams::default()));
engine.set_canvas_rect(Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64));
engine.tick(1.0 / 60.0);
engine.fit_view();
let bytes = render_to_png(&export_spec(), |ctx| {
engine.draw(ctx);
})
.expect("radial graph should render");
assert_eq!(decoded_png_dims(&bytes), (WIDTH, HEIGHT));
write_proof_png("graph_radial.png", &bytes);
}
#[test]
fn collapsed_cluster_renders_supernode_to_a_valid_png() {
let (graph, clusters) = seeded_cluster_fixture();
let mut engine: GraphEngine<(), (), ForceDirectedLayout> = GraphEngine::new(graph, ForceDirectedLayout::default());
let mut positions = vec![(0.0f32, 0.0f32)]; for c in 0..3 {
let angle = c as f32 * 2.094_395; let cx = angle.cos() * 160.0;
let cy = angle.sin() * 160.0;
for m in 0..8 {
let a = m as f32 / 8.0 * std::f32::consts::TAU;
positions.push((cx + a.cos() * 40.0, cy + a.sin() * 40.0));
}
}
engine.seed_positions(&positions);
let group_a = engine.define_cluster(clusters[0].clone()).expect("cluster 0 is non-empty");
engine.define_cluster(clusters[1].clone());
engine.define_cluster(clusters[2].clone());
engine.set_canvas_rect(Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64));
for _ in 0..200 {
engine.tick(1.0 / 60.0);
}
assert!(engine.collapse_cluster(group_a));
engine.fit_view();
let bytes = render_to_png(&export_spec(), |ctx| {
engine.draw(ctx);
})
.expect("collapsed cluster graph should render");
assert_eq!(decoded_png_dims(&bytes), (WIDTH, HEIGHT));
write_proof_png("graph_collapsed.png", &bytes);
}
}