facett-graphgen/src/build.rs
//! **The [`BuildFn`](crate::BuildFn)s** — how each graph component is made from a
//! generated graph ([`crate::GenGraph`]). One function per component, each building the
//! SAME component a host shows on its own data, with the GPU lane it carries switched on
//! (it self-disables with no installed renderer and says so in `state_json`).
//!
//! Moved from `facett-demo/src/gen_build.rs` (lane F, 2026-10-02) so facet-wrapped's
//! `{"generate": …}` seam builds the Streamlit gallery's graphs with the demo's own
//! functions; the demo keeps the builders of its demo-only tab types (`Graphs`, `falkor`,
//! `World coderoom`, `graphview`) and re-exports these.
use facett_graph3d::{Graph3D, Layout3D};
use facett_graphnav::GraphNav;
use facett_graphpan::GraphPanView;
use facett_graphview::DecoratedGraphView;
use facett_lineage::LineageView;
use crate::GenGraph;
/// The viewport a freshly built 2-D canvas frames its model for (the deck pane on a
/// desktop window; the user's ⊙ fit re-frames for the real size).
pub const FIT_VIEWPORT: egui::Vec2 = egui::vec2(1100.0, 620.0);
/// The ceiling of the components on the resident GPU lane / GPU layout (`Graphs`,
/// `lineage`, `decograph`, `graphpan`, `graph3d`; since lane G 2026-10-02 also `falkor`,
/// `World coderoom`, `graph` / facet-wrapped's `graph2d`, `graphview` — the fixture
/// ceiling of 1 000 is gone).
pub const RESIDENT_SCALE: usize = 100_000;
/// `lineage` — every generated edge a dataset-to-dataset run.
pub fn lineage(title: &'static str, gg: &GenGraph) -> LineageView {
let mut v = LineageView::from_rows(title, crate::to_lineage_rows(gg));
v.nav_mut().legibility.gpu_cloud = true;
v
}
/// The bare navigator (`Graphs`' engine, facet-wrapped's `graphnav`): the typed property
/// graph on the generator's placement (the `geographic` chip draws it), the GPU cloud on
/// (resident above `SCALE_NODES`; without an installed renderer it reports
/// `gpu_cloud: false` and the painter it had draws).
pub fn graphnav(title: &'static str, gg: &GenGraph) -> GraphNav {
let mut nav = GraphNav::new(title, crate::to_propgraph(gg)).with_places(crate::to_places(gg));
nav.legibility.gpu_cloud = true;
nav
}
/// `decograph` — the `DecoratedGraphView` (falkor's and the World coderoom's painter)
/// on the generator's placement, framed.
pub fn decograph(title: &'static str, gg: &GenGraph) -> DecoratedGraphView {
let mut v = DecoratedGraphView::new(title).with_model(crate::to_view_model(gg)).with_gpu(true);
v.fit_model(FIT_VIEWPORT, 16.0);
v
}
/// `graphpan` — the bare pan/zoom canvas on the generator's placement, framed.
pub fn graphpan(title: &'static str, gg: &GenGraph) -> GraphPanView {
let model = crate::to_pan_model(gg);
let zoom = fit_zoom(&model);
let mut v = GraphPanView::new(title).with_model(model).with_gpu(true);
let _ = v.update(facett_graphpan::Msg::SetZoom(zoom));
v
}
/// The zoom that frames a graphpan model (centred on world 0,0, as the placement is)
/// inside [`FIT_VIEWPORT`].
fn fit_zoom(model: &facett_graphpan::GraphModel) -> f32 {
let (mut ex, mut ey) = (1.0f32, 1.0f32);
for n in &model.nodes {
ex = ex.max(n.pos.x.abs() + facett_graphpan::BOX_W * 0.5);
ey = ey.max(n.pos.y.abs() + facett_graphpan::BOX_H * 0.5);
}
((FIT_VIEWPORT.x - 32.0) / (2.0 * ex)).min((FIT_VIEWPORT.y - 32.0) / (2.0 * ey)).clamp(0.01, 4.0)
}
/// `graph3d` — the 3-D cloud: a force layout up to 10 000 nodes, the generator's own
/// placement (`Layout3D::Given`) above, so 100 000 nodes need no n-body pass.
pub fn graph3d(title: &'static str, gg: &GenGraph) -> Graph3D {
let (nodes, edges, pos) = crate::to_graph3d(gg);
let g = if nodes.len() > 10_000 {
Graph3D::new(nodes, edges, Layout3D::Given).with_positions(pos)
} else {
Graph3D::new(nodes, edges, Layout3D::Force)
}
.with_title(title)
.with_controls(true);
#[cfg(feature = "graph3d-wgpu")]
let g = g.with_gpu(true);
g
}
/// `graph` — the generated graph as a `facett-graph` scene (its circular layout), on the
/// GPU cloud lane when the build carries it (above `facett_graph::SCALE_ABOVE` nodes the
/// scale path: one layout per scene, a pan/zoom camera, the RESIDENT cloud, AccessKit
/// buttons for the on-screen nodes under `A11Y_BUDGET`).
///
/// Above `SCALE_ABOVE`, with a host-lent layout device, the scene is FORCE-laid on the
/// GPU (the falkor / navigator lane) instead of the circle: 100 000 nodes on one circle
/// are 300 000 screen-wide chords — a white disc, and fill-bound on an iGPU (t14s:
/// 45 fps). With no device the circle stays (no CPU force layout at this size).
pub fn graph(title: &'static str, gg: &GenGraph) -> facett_graph::GraphView {
let g = facett_graph::GraphView::new(crate::to_scene(gg)).with_title(title).empty_hint("no services");
#[cfg(feature = "graph-layout-gpu")]
let g = if gg.g.n > facett_graph::SCALE_ABOVE && facett_graphview::gpu_layout::host_device_installed() {
g.with_layout(facett_core::Layout::Force)
} else {
g
};
#[cfg(feature = "graph-wgpu")]
let g = g.with_gpu(true);
g
}