1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
pub mod cose_bilkent;
pub mod fcose;
#[derive(Debug, Clone)]
pub enum Algorithm {
/// Cytoscape COSE-Bilkent (Mermaid mindmap default).
CoseBilkent(CoseBilkentOptions),
/// Cytoscape FCoSE (Mermaid architecture layout).
Fcose(FcoseOptions),
}
#[derive(Debug, Clone, Default)]
pub struct CoseBilkentOptions {
/// Seed for deterministic randomness. The upstream JS implementation relies on `Math.random`,
/// so the Rust port will use a reproducible RNG here.
pub random_seed: u64,
}
#[derive(Debug, Clone, Default)]
pub struct FcoseOptions {
pub random_seed: u64,
/// Mermaid Architecture runs Cytoscape FCoSE twice (`layout.run()` inside `layoutstop`),
/// which advances the seeded `Math.random()` stream and can change the final coordinates.
///
/// When enabled, the Rust port mimics that behavior by performing two consecutive runs while
/// keeping the RNG stream continuous between runs.
pub rerun: bool,
/// Override for layout-base/CoSE `DEFAULT_EDGE_LENGTH` (used for repulsion/grid range, overlap
/// separation buffer, and convergence thresholds).
///
/// In upstream Cytoscape FCoSE, `DEFAULT_EDGE_LENGTH` is derived from the `idealEdgeLength`
/// option (before inter-graph nesting/smart adjustments), then used by layout-base constants
/// such as `MIN_REPULSION_DIST` and the FR-grid cell size. Keeping this value aligned is
/// important for parity with Mermaid-generated SVG baselines.
pub default_edge_length: Option<f64>,
pub alignment_constraint: Option<AlignmentConstraint>,
pub relative_placement_constraint: Vec<RelativePlacementConstraint>,
/// Optional padding applied around compound (group) bounds when computing compound repulsion.
pub compound_padding: Option<f64>,
/// Optional override for the "original component center" used by `aux.relocateComponent(...)`.
///
/// In upstream Cytoscape FCoSE, `originalCenter` comes from `eles.boundingBox()` before the
/// layout runs, and the final layout is translated so the component's bounding box center
/// matches that pre-layout center.
///
/// When set, the Rust port uses this value instead of the layout-base bounds center.
pub relocate_center: Option<(f64, f64)>,
}
#[derive(Debug, Clone, Default)]
pub struct AlignmentConstraint {
/// Nodes in each inner vec share the same y coordinate (horizontal alignment).
pub horizontal: Vec<Vec<String>>,
/// Nodes in each inner vec share the same x coordinate (vertical alignment).
pub vertical: Vec<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct RelativePlacementConstraint {
pub left: Option<String>,
pub right: Option<String>,
pub top: Option<String>,
pub bottom: Option<String>,
pub gap: f64,
}