Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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
}