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

nordisk/facett

facett-flow/src/labels.rs

use egui::{FontId, Pos2, Rect, vec2};
use facett_core::legibility::{LabelCandidate, PlaceOpts, place_labels};
pub(crate) fn paint(
    ui: &egui::Ui,
    rect: Rect,
    graph: &crate::Graph,
    scene: &crate::scene::Scene,
    scale: f32,
    centre: Pos2,
) {
    let world = Rect::from_min_max(
        centre + (rect.min - rect.center()) / scale,
        centre + (rect.max - rect.center()) / scale,
    );
    let indices = scene.visible(world, 2000);
    let color = facett_core::theme(ui).text;
    let galleys: Vec<_> = indices
        .iter()
        .map(|&i| {
            ui.painter().layout_no_wrap(
                graph.nodes[i].label.clone(),
                FontId::proportional(11.0),
                color,
            )
        })
        .collect();
    let candidates: Vec<_> = indices
        .iter()
        .zip(&galleys)
        .map(|(&i, g)| {
            LabelCandidate::new(
                rect.center()
                    + (Pos2::from(graph.nodes[i].position) - centre) * scale
                    + vec2(0.0, 12.0),
                g.size(),
                0.0,
            )
        })
        .collect();
    for label in place_labels(&candidates, rect, PlaceOpts::default()).placed {
        ui.painter().with_clip_rect(rect).galley(
            label.rect.center() - galleys[label.index].size() * 0.5,
            galleys[label.index].clone(),
            color,
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn ten_thousand_node_label_pass_places_nonoverlapping_text() {
        let graph = crate::Graph::demo(10_000);
        let scene = crate::scene::Scene::new(&graph);
        let rect = Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1000.0, 700.0));
        let scale =
            (rect.width() / scene.bounds.width()).min(rect.height() / scene.bounds.height());
        let ctx = egui::Context::default();
        let mut output = ctx.run_ui(
            egui::RawInput {
                screen_rect: Some(rect),
                ..Default::default()
            },
            |ui| paint(ui, rect, &graph, &scene, scale, scene.bounds.center()),
        );
        let texts: Vec<_> = output
            .shapes
            .iter()
            .filter_map(|s| match &s.shape {
                egui::Shape::Text(t) => Some(
                    Rect::from_min_size(t.pos, t.galley.size())
                        .intersect(s.clip_rect)
                        .intersect(rect),
                ),
                _ => None,
            })
            .filter(|r| r.is_positive())
            .collect();
        output.textures_delta.clear();
        assert!(!texts.is_empty());
        assert!(texts.len() <= 2000);
        for (i, a) in texts.iter().enumerate() {
            assert!(rect.intersects(*a));
            for b in &texts[i + 1..] {
                assert!(!a.intersects(*b), "placed labels overlap: {a:?}, {b:?}");
            }
        }
        output.textures_delta.clear();
    }
}