mmdflux 2.1.0

Render Mermaid diagrams as Unicode text, ASCII, SVG, and MMDS JSON.
Documentation
use std::collections::HashMap;

use super::intersect::{NodeFace, classify_face};
use super::{GridLayout, NodeBounds, SubgraphBounds};
use crate::graph::{Direction, Edge, Shape};

pub(crate) type NodeContainingSubgraphMap<'a> = HashMap<&'a str, &'a str>;

pub(crate) fn subgraph_edge_face(
    bounds: &NodeBounds,
    other: &NodeBounds,
    direction: Direction,
) -> NodeFace {
    let bounds_right = bounds.x + bounds.width.saturating_sub(1);
    let bounds_bottom = bounds.y + bounds.height.saturating_sub(1);
    let other_right = other.x + other.width.saturating_sub(1);
    let other_bottom = other.y + other.height.saturating_sub(1);

    match direction {
        Direction::TopDown | Direction::BottomTop => {
            if other_bottom < bounds.y {
                return NodeFace::Top;
            }
            if other.y > bounds_bottom {
                return NodeFace::Bottom;
            }
            if other_right < bounds.x {
                return NodeFace::Left;
            }
            if other.x > bounds_right {
                return NodeFace::Right;
            }
        }
        Direction::LeftRight | Direction::RightLeft => {
            if other_right < bounds.x {
                return NodeFace::Left;
            }
            if other.x > bounds_right {
                return NodeFace::Right;
            }
            if other_bottom < bounds.y {
                return NodeFace::Top;
            }
            if other.y > bounds_bottom {
                return NodeFace::Bottom;
            }
        }
    }

    classify_face(
        bounds,
        (other.center_x(), other.center_y()),
        Shape::Rectangle,
    )
}

pub(crate) fn subgraph_bounds_as_node(bounds: &SubgraphBounds) -> NodeBounds {
    NodeBounds {
        x: bounds.x,
        y: bounds.y,
        width: bounds.width,
        height: bounds.height,
        layout_center_x: None,
        layout_center_y: None,
    }
}

pub(crate) fn resolve_edge_bounds(
    layout: &GridLayout,
    edge: &Edge,
) -> Option<(NodeBounds, NodeBounds)> {
    let from_bounds = if let Some(sg_id) = edge.from_subgraph.as_ref() {
        layout
            .subgraph_bounds
            .get(sg_id)
            .map(subgraph_bounds_as_node)?
    } else {
        *layout.get_bounds(&edge.from)?
    };
    let to_bounds = if let Some(sg_id) = edge.to_subgraph.as_ref() {
        layout
            .subgraph_bounds
            .get(sg_id)
            .map(subgraph_bounds_as_node)?
    } else {
        *layout.get_bounds(&edge.to)?
    };
    Some((from_bounds, to_bounds))
}

pub(crate) fn bounds_for_node_id(layout: &GridLayout, node_id: &str) -> Option<NodeBounds> {
    if let Some(bounds) = layout.get_bounds(node_id) {
        return Some(*bounds);
    }
    layout
        .subgraph_bounds
        .get(node_id)
        .map(subgraph_bounds_as_node)
}

pub(crate) fn node_inside_subgraph(bounds: &NodeBounds, sg: &SubgraphBounds) -> bool {
    let node_right = bounds.x + bounds.width;
    let node_bottom = bounds.y + bounds.height;
    let sg_right = sg.x + sg.width;
    let sg_bottom = sg.y + sg.height;
    bounds.x >= sg.x && bounds.y >= sg.y && node_right <= sg_right && node_bottom <= sg_bottom
}

pub(crate) fn containing_subgraph_id_uncached<'a>(
    layout: &'a GridLayout,
    node_id: &str,
) -> Option<&'a str> {
    let bounds = layout.node_bounds.get(node_id)?;
    layout
        .subgraph_bounds
        .iter()
        .filter(|(_, sg)| node_inside_subgraph(bounds, sg))
        .max_by_key(|(_, sg)| (sg.depth, usize::MAX - (sg.width * sg.height)))
        .map(|(id, _)| id.as_str())
}

pub(crate) fn containing_subgraph_id<'a>(
    layout: &'a GridLayout,
    node_id: &str,
    node_containing_subgraph: Option<&NodeContainingSubgraphMap<'a>>,
) -> Option<&'a str> {
    node_containing_subgraph
        .and_then(|map| map.get(node_id).copied())
        .or_else(|| containing_subgraph_id_uncached(layout, node_id))
}

pub(crate) fn build_node_containing_subgraph_map<'a>(
    layout: &'a GridLayout,
) -> NodeContainingSubgraphMap<'a> {
    layout
        .node_bounds
        .keys()
        .filter_map(|node_id| {
            containing_subgraph_id_uncached(layout, node_id.as_str())
                .map(|subgraph_id| (node_id.as_str(), subgraph_id))
        })
        .collect()
}