diff --git a/Cargo.lock b/Cargo.lock index 1e897c2..b82d661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,8 +272,6 @@ dependencies = [ [[package]] name = "dagre_rust" version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0e44f22d2a32979a1265aa1a70f6e2f4f43ec0d562d483a1c818f9a4121926" dependencies = [ "graphlib_rust", "ordered_hashmap", @@ -576,7 +574,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror", + "thiserror 2.0.18", "unicode-normalization", ] @@ -684,7 +682,8 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mermaid-rs-renderer" version = "0.1.2" -source = "git+https://github.com/1jehuang/mermaid-rs-renderer#66b7d8c3f3152153666d589e789a143e87afcec5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809edd3a9b85b6ae4bbe646317c2de9ea2ff7cb984e4e064625e048d9b1ef7f4" dependencies = [ "anyhow", "dagre_rust", @@ -694,7 +693,7 @@ dependencies = [ "regex", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -708,7 +707,7 @@ dependencies = [ "mermaid-rs-renderer", "pulldown-cmark", "serde", - "thiserror", + "thiserror 2.0.18", "toml 0.8.23", "tree-sitter", "tree-sitter-bash", @@ -1376,13 +1375,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -1523,7 +1542,7 @@ checksum = "bb0636662a03005d9289649e0b4a89ff37b75df5033e8d4a16398740ae6496d2" dependencies = [ "regex", "streaming-iterator", - "thiserror", + "thiserror 2.0.18", "tree-sitter", ] diff --git a/Cargo.toml b/Cargo.toml index 3ef4eed..5af601e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,4 +37,8 @@ serde = { version = "1", features = ["derive"] } toml = "0.8" # Diagram rendering -mermaid-rs-renderer = { git = "https://github.com/1jehuang/mermaid-rs-renderer", default-features = false } +mermaid-rs-renderer = { version = "0.1", default-features = false } + +# Patch dagre_rust to fix unwrap on None bug +[patch.crates-io] +dagre_rust = { path = "patches/dagre_rust" } diff --git a/patches/dagre_rust/.cargo-ok b/patches/dagre_rust/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/patches/dagre_rust/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/patches/dagre_rust/.cargo_vcs_info.json b/patches/dagre_rust/.cargo_vcs_info.json new file mode 100644 index 0000000..837c8f7 --- /dev/null +++ b/patches/dagre_rust/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "62c894d556f29ddc2c4f97047e511907290a56d3" + }, + "path_in_vcs": "" +} diff --git a/patches/dagre_rust/.gitignore b/patches/dagre_rust/.gitignore new file mode 100644 index 0000000..2a0038a --- /dev/null +++ b/patches/dagre_rust/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/patches/dagre_rust/Cargo.toml b/patches/dagre_rust/Cargo.toml new file mode 100644 index 0000000..d764f14 --- /dev/null +++ b/patches/dagre_rust/Cargo.toml @@ -0,0 +1,28 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +authors = ["Ameer Hamza "] +categories = ["graph", "dagre"] +description = "Dagre implementation in Rust" +edition = "2021" +keywords = ["dagre", "graph", "dag"] +license = "Apache-2.0" +name = "dagre_rust" +readme = "README.md" +repository = "https://github.com/r3alst/dagre-rust" +version = "0.0.5" + +[dependencies.graphlib_rust] +version = "0.0.2" + +[dependencies.ordered_hashmap] +version = "0.0.3" diff --git a/patches/dagre_rust/Cargo.toml.orig b/patches/dagre_rust/Cargo.toml.orig new file mode 100644 index 0000000..af46d85 --- /dev/null +++ b/patches/dagre_rust/Cargo.toml.orig @@ -0,0 +1,14 @@ +[package] +name = "dagre_rust" +version = "0.0.5" +authors = ["Ameer Hamza "] +categories = ["graph", "dagre"] +description = "Dagre implementation in Rust" +keywords = ["dagre", "graph", "dag"] +license = "Apache-2.0" +repository = "https://github.com/r3alst/dagre-rust" +edition = "2021" + +[dependencies] +graphlib_rust = "0.0.2" +ordered_hashmap = "0.0.3" \ No newline at end of file diff --git a/patches/dagre_rust/LICENCE b/patches/dagre_rust/LICENCE new file mode 100644 index 0000000..5c71319 --- /dev/null +++ b/patches/dagre_rust/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Ameer Hamza + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/patches/dagre_rust/README.md b/patches/dagre_rust/README.md new file mode 100644 index 0000000..3cc2e72 --- /dev/null +++ b/patches/dagre_rust/README.md @@ -0,0 +1,9 @@ +# dagre-rust + +dagre-rust is a Rust implementation of [graphlib](https://github.com/dagrejs/dagre) that helps drawing DAG. + +# License + +Dagre is licensed under the terms of the Apache-2.0 License. See the +[LICENSE](LICENSE) file +for details. diff --git a/patches/dagre_rust/src/layout/acyclic.rs b/patches/dagre_rust/src/layout/acyclic.rs new file mode 100644 index 0000000..1e3704c --- /dev/null +++ b/patches/dagre_rust/src/layout/acyclic.rs @@ -0,0 +1,85 @@ +use crate::layout::util::unique_id; +use crate::layout::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::{Edge, Graph}; +use ordered_hashmap::OrderedHashMap; + +pub fn run(graph: &mut Graph) { + let mut fas: Option> = None; + let graph_config = graph.graph(); + if graph_config.acyclicer.is_some() + && graph_config.acyclicer.clone().unwrap() == "greedy".to_string() + { + // TODO: need to implement this algorithm + println!("greedy_fas"); + // greedyFAS + } else { + fas = Some(dfs_fas(graph)); + // println!("dfs_fas"); + } + + let _fas = fas.unwrap_or(vec![]); + for edge in _fas { + let _edge_label = graph.edge_with_obj(&edge); + if _edge_label.is_none() { + continue; + } + let mut edge_label = _edge_label.cloned().unwrap(); + graph.remove_edge_with_obj(&edge); + edge_label.forward_name = edge.name.clone(); + edge_label.reversed = Some(true); + let _ = graph.set_edge( + &edge.w, + &edge.v, + Some(edge_label), + Some(format!("rev{}", unique_id())), + ); + } +} + +fn dfs_fas(graph: &mut Graph) -> Vec { + let mut fas: Vec = vec![]; + let mut stack: OrderedHashMap = OrderedHashMap::new(); + let mut visited: OrderedHashMap = OrderedHashMap::new(); + + fn dfs( + node_id: String, + stack: &mut OrderedHashMap, + visited: &mut OrderedHashMap, + graph: &mut Graph, + fas: &mut Vec, + ) { + if visited.contains_key(&node_id) { + return (); + } + + visited.insert(node_id.clone(), true); + stack.insert(node_id.clone(), true); + let out_edges = graph.out_edges(&node_id, None).unwrap_or(vec![]); + for edge in out_edges.into_iter() { + if stack.contains_key(&edge.w) { + fas.push(edge.clone()); + } else { + dfs(edge.w.clone(), stack, visited, graph, fas); + } + } + stack.remove(&node_id); + } + + for node_id in graph.nodes() { + dfs(node_id, &mut stack, &mut visited, graph, &mut fas); + } + return fas; +} + +pub fn undo(g: &mut Graph) { + for e in g.edges() { + let edge = g.edge_mut_with_obj(&e).unwrap(); + if edge.reversed.clone().unwrap_or(false) { + let forward_name = edge.forward_name.clone().unwrap(); + let mut label = edge.clone(); + label.reversed = None; + label.forward_name = None; + let _ = g.set_edge(&e.w, &e.v, Some(label), Some(forward_name)); + } + } +} diff --git a/patches/dagre_rust/src/layout/add_border_segments.rs b/patches/dagre_rust/src/layout/add_border_segments.rs new file mode 100644 index 0000000..28f0c9e --- /dev/null +++ b/patches/dagre_rust/src/layout/add_border_segments.rs @@ -0,0 +1,72 @@ +use crate::layout::util::add_dummy_node; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::graph::GRAPH_NODE; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +#[derive(Debug, Clone, PartialEq)] +pub enum BorderTypeName { + BorderLeft, + BorderRight, +} + +pub fn add_border_segments(g: &mut Graph) { + fn dfs(v: &String, g: &mut Graph) { + let children = g.children(v); + if children.len() > 0 { + for cv in children.iter() { + dfs(cv, g); + } + } + let node = g.node_mut(v).unwrap(); + if node.min_rank.is_some() { + node.border_left = Some(OrderedHashMap::new()); + node.border_right = Some(OrderedHashMap::new()); + let mut rank = node.min_rank.clone().unwrap_or(0); + let max_rank = node.max_rank.clone().unwrap_or(0) + 1; + while rank < max_rank { + add_border_node(g, BorderTypeName::BorderLeft, "_bl", v, &rank); + rank += 1; + } + } + } + + let children = g.children(&GRAPH_NODE.to_string()); + for v in children.iter() { + dfs(v, g); + } +} + +fn add_border_node( + g: &mut Graph, + prop: BorderTypeName, + prefix: &str, + sg: &String, + rank: &i32, +) { + let mut label = GraphNode::default(); + label.rank = Some(rank.clone()); + label.border_type = Some(prop.clone()); + + let curr = add_dummy_node(g, "border".to_string(), label, prefix.to_string()); + + let sg_node = g.node_mut(sg).unwrap(); + let mut border = sg_node.border_left.as_mut().unwrap(); + match prop { + BorderTypeName::BorderRight => { + border = sg_node.border_right.as_mut().unwrap(); + } + _ => (), + } + border.insert(rank.clone(), curr.clone()); + + let prev = border.get(&(rank - 1)); + if prev.is_some() { + let prev_v = prev.cloned().unwrap(); + let mut graph_edge = GraphEdge::default(); + graph_edge.weight = Some(1.0); + let _ = g.set_edge(&prev_v, &curr, Some(graph_edge), None); + } + + let _ = g.set_parent(&curr, Some(sg.clone())); +} diff --git a/patches/dagre_rust/src/layout/coordinate_system.rs b/patches/dagre_rust/src/layout/coordinate_system.rs new file mode 100644 index 0000000..2dc4b2d --- /dev/null +++ b/patches/dagre_rust/src/layout/coordinate_system.rs @@ -0,0 +1,89 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; + +pub fn adjust(g: &mut Graph) { + let rank_dir = g.graph().rankdir.clone().unwrap(); + if &rank_dir == "lr" || &rank_dir == "rl" { + swap_width_height(g); + } +} + +pub fn undo(g: &mut Graph) { + let rank_dir = g.graph().rankdir.clone().unwrap(); + if &rank_dir == "bt" || &rank_dir == "rl" { + reverse_y(g); + } + + if &rank_dir == "lr" || &rank_dir == "rl" { + swap_x_y(g); + swap_width_height(g); + } +} + +fn swap_width_height(g: &mut Graph) { + let nodes = g.nodes(); + // =swapWidthHeightOne + nodes.iter().for_each(|v| { + let node = g.node_mut(v).unwrap(); + let w = node.width.clone(); + node.width = node.height; + node.height = w; + }); + let edges = g.edges(); + // =swapWidthHeightOne + edges.iter().for_each(|e| { + let edge_label = g.edge_mut_with_obj(&e).unwrap(); + let w = edge_label.width.clone(); + edge_label.width = edge_label.height; + edge_label.height = w; + }); +} + +fn reverse_y(g: &mut Graph) { + let nodes = g.nodes(); + nodes.iter().for_each(|v| { + // =reverseYOne + let node = g.node_mut(v).unwrap(); + node.y = -node.y; + }); + + let edges = g.edges(); + edges.iter().for_each(|e| { + // =reverseYOne + let edge_label = g.edge_mut_with_obj(&e).unwrap(); + let mut points = edge_label.points.clone().unwrap_or(vec![]); + points.iter_mut().for_each(|point| { + point.y = -point.y; + }); + edge_label.points = Some(points); + edge_label.y = -edge_label.y; + }); +} + +fn swap_x_y(g: &mut Graph) { + let nodes = g.nodes(); + nodes.iter().for_each(|v| { + // =swapXYOne + let node = g.node_mut(v).unwrap(); + let x = node.x.clone(); + node.x = node.y; + node.y = x; + }); + + let edges = g.edges(); + edges.iter().for_each(|e| { + // =swapXYOne + let edge_label = g.edge_mut_with_obj(&e).unwrap(); + let mut points = edge_label.points.clone().unwrap_or(vec![]); + points.iter_mut().for_each(|point| { + let x = point.x.clone(); + point.x = point.y; + point.y = x; + }); + edge_label.points = Some(points); + + let x = edge_label.x.clone(); + edge_label.x = edge_label.y; + edge_label.y = x; + }); +} diff --git a/patches/dagre_rust/src/layout/mod.rs b/patches/dagre_rust/src/layout/mod.rs new file mode 100644 index 0000000..155722a --- /dev/null +++ b/patches/dagre_rust/src/layout/mod.rs @@ -0,0 +1,623 @@ +use crate::layout::add_border_segments::add_border_segments; +use crate::layout::order::order; +use crate::layout::parent_dummy_chains::parent_dummy_chains; +use crate::layout::rank::rank; +use crate::layout::util::{ + as_non_compound_graph, intersect_rect, normalize_ranks, remove_empty_ranks, + transfer_node_edge_labels, Rect, +}; +use crate::{GraphConfig, GraphEdge, GraphEdgePoint, GraphNode}; +use graphlib_rust::{Graph, GraphOption}; + +pub mod acyclic; +pub mod add_border_segments; +pub mod coordinate_system; +pub mod nesting_graph; +pub mod normalize; +pub mod order; +pub mod parent_dummy_chains; +pub mod position; +pub mod rank; +pub mod util; + +const DEFAULT_RANK_SEP: f32 = 50.0; + +pub fn layout(g: &mut Graph) { + let mut layout_graph = build_layout_graph(g); + run_layout(&mut layout_graph); + update_input_graph(g, &layout_graph); +} + +pub fn update_input_graph( + input_graph: &mut Graph, + layout_graph: &Graph, +) { + for v in input_graph.nodes() { + let input_label_ = input_graph.node_mut(&v); + let layout_label = layout_graph.node(&v).unwrap(); + + if let Some(input_label) = input_label_ { + input_label.x = layout_label.x; + input_label.y = layout_label.y; + + if layout_graph.children(&v).len() > 0 { + input_label.width = layout_label.width; + input_label.height = layout_label.height; + } + } + } + + for e in input_graph.edges() { + let input_label = input_graph.edge_mut_with_obj(&e).unwrap(); + let layout_label = layout_graph.edge_with_obj(&e).unwrap(); + + input_label.points = layout_label.points.clone(); + input_label.x = layout_label.x; + input_label.y = layout_label.y; + } + + input_graph.graph_mut().width = layout_graph.graph().width; + input_graph.graph_mut().height = layout_graph.graph().height; +} + +pub fn set_graph_label_default_values(graph_label: &mut GraphConfig) { + if graph_label.ranksep.is_none() { + graph_label.ranksep = Some(50.0); + } + + if graph_label.edgesep.is_none() { + graph_label.edgesep = Some(20.0); + } + + if graph_label.nodesep.is_none() { + graph_label.nodesep = Some(50.0); + } + + if graph_label.rankdir.is_none() { + graph_label.rankdir = Some("tb".to_string()); + } + + if graph_label.marginx.is_none() { + graph_label.marginx = Some(0.0); + } + + if graph_label.marginy.is_none() { + graph_label.marginy = Some(0.0); + } +} + +pub fn set_edge_label_default_values(edge_label: &mut GraphEdge) { + if edge_label.minlen.is_none() { + edge_label.minlen = Some(1.0); + } + + if edge_label.weight.is_none() { + edge_label.weight = Some(1.0); + } + + if edge_label.width.is_none() { + edge_label.width = Some(0.0); + } + + if edge_label.height.is_none() { + edge_label.height = Some(0.0); + } + + if edge_label.labeloffset.is_none() { + edge_label.labeloffset = Some(10.0); + } + + if edge_label.labelpos.is_none() { + edge_label.labelpos = Some("r".to_string()); + } +} + +/* + * Constructs a new graph from the input graph, which can be used for layout. + * This process copies only whitelisted attributes from the input graph to the + * layout graph. Thus this function serves as a good place to determine what + * attributes can influence layout. + */ +pub fn build_layout_graph( + input_graph: &Graph, +) -> Graph { + let mut g: Graph = Graph::new(Some(GraphOption { + directed: Some(true), + multigraph: Some(true), + compound: Some(true), + })); + + let mut graph_label: GraphConfig = input_graph.graph().clone(); + set_graph_label_default_values(&mut graph_label); + g.set_graph(graph_label); + + for node_id in input_graph.nodes().iter() { + let _node = input_graph.node(node_id); + if _node.is_none() { + continue; + } + g.set_node(node_id.clone(), _node.cloned()); + let _ = g.set_parent(node_id, input_graph.parent(node_id).cloned()); + } + + for edge_obj in input_graph.edges() { + let _edge = input_graph.edge_with_obj(&edge_obj); + if _edge.is_none() { + continue; + } + let mut edge_label = _edge.cloned().unwrap(); + set_edge_label_default_values(&mut edge_label); + let _ = g.set_edge_with_obj(&edge_obj, Some(edge_label)); + } + + return g; +} + +/* + * This idea comes from the Gansner paper: to account for edge labels in our + * layout we split each rank in half by doubling minlen and halving ranksep. + * Then we can place labels at these mid-points between nodes. + * + * We also add some minimal padding to the width to push the label for the edge + * away from the edge itself a bit. + */ +pub fn make_space_for_edge_labels(graph: &mut Graph) { + let graph_config = graph.graph_mut(); + graph_config.ranksep = Some(graph_config.ranksep.unwrap_or(DEFAULT_RANK_SEP) / 2.0); + + // moving in nested block due to borrow checker + { + let graph_config = graph.graph().clone(); + let edge_objs = graph.edges(); + for edge_obj in edge_objs.into_iter() { + let _edge = graph.edge_mut_with_obj(&edge_obj); + if _edge.is_none() { + continue; + } + let edge = _edge.unwrap(); + + let minlen = edge.minlen.unwrap_or(1.0); + let labelpos = edge.labelpos.clone().unwrap_or("".to_string()); + let labeloffset = edge.labeloffset.unwrap_or(10.0); + let rankdir = graph_config.rankdir.clone().unwrap_or("".to_string()); + + edge.minlen = Some(minlen * 2.0); + if labelpos != "c" { + if rankdir == "tb" || rankdir == "bt" { + edge.width = Some(edge.width.unwrap_or(0.0) + labeloffset); + } else { + edge.height = Some(edge.height.unwrap_or(0.0) + labeloffset); + } + } + } + } +} + +/* + * Creates temporary dummy nodes that capture the rank in which each edge's + * label is going to, if it has one of non-zero width and height. We do this + * so that we can safely remove empty ranks while preserving balance for the + * label's position. + */ +pub fn inject_edge_label_proxies(g: &mut Graph) { + let edges = g.edges(); + for e in edges.into_iter() { + let edge_ = g.edge_with_obj(&e); + if let Some(edge) = edge_ { + if edge.width.clone().unwrap_or(0.0) > 0.0 && edge.height.clone().unwrap_or(0.0) > 0.0 { + let v = g.node(&e.v); + let w = g.node(&e.v); + let v_rank = v.cloned().unwrap_or(GraphNode::default()).rank.unwrap_or(0); + let w_rank = w.cloned().unwrap_or(GraphNode::default()).rank.unwrap_or(0); + let mut label = GraphNode::default(); + label.rank = Some((w_rank - v_rank) / 2 + v_rank); + util::add_dummy_node(g, "edge-proxy".to_string(), label, "_ep".to_string()); + } + } + } +} + +pub fn assign_rank_min_max(g: &mut Graph) { + let mut max_rank = 0; + let vs = g.nodes(); + for v in vs.iter() { + let node_ = g.node(v); + if node_.is_none() { + continue; + } + let node = node_.unwrap(); + if node.border_top.is_some() { + let border_top = node.border_top.clone().unwrap(); + let border_bottom = node.border_bottom.clone().unwrap(); + let _min_rank = g.node(&border_top).cloned().unwrap().rank.unwrap_or(0); + let _max_rank = g.node(&border_bottom).cloned().unwrap().rank.unwrap_or(0); + + let _node = g.node_mut(v).unwrap(); + _node.min_rank = Some(_min_rank); + _node.max_rank = Some(_max_rank.clone()); + max_rank = std::cmp::max(max_rank, _max_rank); + } + } +} + +enum GraphElement<'a> { + Node(&'a GraphNode), + Edge(&'a GraphEdge), +} + +impl<'a> GraphElement<'a> { + fn x(&self) -> f32 { + match self { + GraphElement::Node(node) => node.x, + GraphElement::Edge(edge) => edge.x, + } + } + + fn y(&self) -> f32 { + match self { + GraphElement::Node(node) => node.y, + GraphElement::Edge(edge) => edge.y, + } + } + + fn width(&self) -> f32 { + match self { + GraphElement::Node(node) => node.width, + GraphElement::Edge(edge) => edge.width.unwrap_or(0.0), + } + } + + fn height(&self) -> f32 { + match self { + GraphElement::Node(node) => node.height, + GraphElement::Edge(edge) => edge.height.unwrap_or(0.0), + } + } +} + +pub fn translate_graph(g: &mut Graph) { + let mut min_x = f64::INFINITY as f32; + let mut max_x: f32 = 0.0; + let mut min_y = f64::INFINITY as f32; + let mut max_y: f32 = 0.0; + let mut graph_label = g.graph().clone(); + let margin_x = graph_label.marginx.unwrap_or(0.0); + let margin_y = graph_label.marginy.unwrap_or(0.0); + + fn get_extremes( + attrs: &GraphElement, + min_x: &mut f32, + max_x: &mut f32, + min_y: &mut f32, + max_y: &mut f32, + ) { + let x = attrs.x(); + let y = attrs.y(); + let w = attrs.width(); + let h = attrs.height(); + *min_x = min_x.min(x - w / 2.0); + *max_x = max_x.max(x + w / 2.0); + *min_y = min_y.min(y - h / 2.0); + *max_y = max_y.max(y + h / 2.0); + } + + for v in g.nodes() { + get_extremes( + &GraphElement::Node(g.node(&v).unwrap()), + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + ); + } + + for e in g.edges() { + let edge = g.edge_with_obj(&e).unwrap(); + if edge.x != 0.0 { + get_extremes( + &GraphElement::Edge(edge), + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + ); + } + } + + min_x -= margin_x; + min_y -= margin_y; + + for v in g.nodes() { + let node = g.node_mut(&v).unwrap(); + node.x -= min_x; + node.y -= min_y; + } + + for e in g.edges() { + let edge = g.edge_mut_with_obj(&e).unwrap(); + if edge.points.is_some() { + for p in edge.points.as_mut().unwrap() { + p.x -= min_x; + p.y -= min_y; + } + } + if edge.x != 0.0 { + edge.x = min_x; + } + + if edge.y != 0.0 { + edge.y = min_y; + } + } + + graph_label.width = max_x - min_x + margin_x; + graph_label.height = max_y - min_y + margin_y; + + g.set_graph(graph_label); +} + +pub fn assign_node_intersects(g: &mut Graph) { + for e in g.edges() { + let mut edge = g.edge_mut_with_obj(&e).cloned().unwrap(); + let node_v = g.node(&e.v).cloned().unwrap(); + let node_w = g.node(&e.w).cloned().unwrap(); + let (p1, p2) = if edge.points.is_none() { + edge.points = Some(vec![]); + ( + GraphEdgePoint { + x: node_w.x, + y: node_w.y, + }, + GraphEdgePoint { + x: node_v.x, + y: node_v.y, + }, + ) + } else { + let points = edge.points.clone().unwrap(); + let r1 = GraphEdgePoint { + x: points[0].x, + y: points[0].y, + }; + + let r2 = GraphEdgePoint { + x: points[points.len() - 1].x, + y: points[points.len() - 1].y, + }; + + (r1, r2) + }; + + let points = edge.points.as_mut().unwrap(); + points.insert( + 0, + intersect_rect( + &Rect { + x: node_v.x, + y: node_v.y, + width: node_v.width, + height: node_v.height, + }, + &p1, + ), + ); + + points.push(intersect_rect( + &Rect { + x: node_w.x, + y: node_w.y, + width: node_w.width, + height: node_w.height, + }, + &p2, + )); + + let _ = g.set_edge_with_obj(&e, Some(edge)); + } +} + +pub fn remove_edge_label_proxies(g: &mut Graph) { + let vs = g.nodes(); + for v in vs.iter() { + let node = g.node(v).unwrap(); + if node.dummy.is_some() && node.dummy.clone().unwrap() == "edge-proxy" { + let rank = node.rank.unwrap_or(0); + if let Some(edge_obj) = node.e.clone() { + if let Some(graph_edge) = g.edge_mut_with_obj(&edge_obj) { + graph_edge.label_rank = Some(rank); + } + } + g.remove_node(v); + } + } +} + +pub fn fixup_edge_label_coords(g: &mut Graph) { + g.edges().iter().for_each(|e| { + let edge = g.edge_mut_with_obj(&e.to_owned()).unwrap(); + if edge.x != 0.0 { + let labelpos = edge.labelpos.clone().unwrap_or("".to_string()); + let labeloffset = edge.labeloffset.clone().unwrap_or(0.0); + if labelpos == "l" || labelpos == "r" { + edge.width = Some(edge.width.unwrap_or(0.0) - labeloffset); + } + + if labelpos == "l" { + edge.x -= edge.width.clone().unwrap_or(0.0) / 2.0 + labeloffset; + } else if labelpos == "r" { + edge.x += edge.width.clone().unwrap_or(0.0) / 2.0 + labeloffset; + } + } + }); +} + +pub fn reverse_points_for_reversed_edges(g: &mut Graph) { + for e in g.edges() { + let edge = g.edge_mut_with_obj(&e.to_owned()).unwrap(); + if edge.reversed.clone().unwrap_or(false) { + if edge.points.is_some() { + let points = edge.points.as_mut().unwrap(); + points.reverse(); + } + } + } +} + +pub fn remove_border_nodes(g: &mut Graph) { + for v in g.nodes() { + if g.children(&v).len() > 0 { + let mut node = g.node(&v).cloned().unwrap(); + let t = g.node(node.border_top.as_ref().unwrap()).cloned().unwrap(); + let b = g + .node(node.border_bottom.as_ref().unwrap()) + .cloned() + .unwrap(); + // TODO: improve this after wasm implementation + let border_left = node.border_left.clone().unwrap(); + let mut l_keys: Vec = border_left.keys().cloned().collect(); + l_keys.sort(); + let border_right = node.border_right.clone().unwrap(); + let mut r_keys: Vec = border_right.keys().cloned().collect(); + r_keys.sort(); + let l = g + .node(border_left.get(&l_keys[l_keys.len() - 1]).unwrap()) + .cloned() + .unwrap(); + let r = g + .node(border_right.get(&r_keys[r_keys.len() - 1]).unwrap()) + .cloned() + .unwrap(); + + node.width = (r.x - l.x).abs(); + node.height = (b.y - t.y).abs(); + node.x = l.x + node.width / 2.0; + node.y = l.y + node.height / 2.0; + + g.set_node(v.clone(), Some(node)); + } + } + + g.nodes().iter().for_each(|v| { + let node = g.node(v).unwrap(); + if node.dummy.is_some() && node.dummy.clone().unwrap() == "border" { + g.remove_node(v); + } + }); +} + +pub fn remove_self_edges(graph: &mut Graph) { + let edge_objs = graph.edges(); + for edge_obj in edge_objs.into_iter() { + if edge_obj.v == edge_obj.w { + let edge_label = graph.edge_with_obj(&edge_obj).cloned().unwrap(); + let node = graph.node_mut(&edge_obj.v).unwrap(); + node.self_edges.push((edge_obj.clone(), edge_label)); + graph.remove_edge_with_obj(&edge_obj); + } + } +} + +pub fn insert_self_edges(graph: &mut Graph) { + let layers = util::build_layer_matrix(graph); + layers.iter().for_each(|layer| { + let mut order_shift = 0; + layer.iter().enumerate().for_each(|(i, v)| { + let node = graph.node_mut(v).unwrap(); + node.order = Some(i + order_shift); + let rank = node.rank.clone(); + + let self_edges = node.self_edges.clone(); + self_edges.into_iter().for_each(|(edge, graph_edge)| { + let mut _graph_node = GraphNode::default(); + _graph_node.width = graph_edge.width.clone().unwrap_or(0.0); + _graph_node.height = graph_edge.height.clone().unwrap_or(0.0); + _graph_node.rank = rank.clone(); + order_shift += 1; + _graph_node.order = Some(i + order_shift); + _graph_node.e = Some(edge.clone()); + _graph_node.label = Some(graph_edge.clone()); + util::add_dummy_node( + graph, + "selfedge".to_string(), + _graph_node, + "_se".to_string(), + ); + }); + }); + }) +} + +pub fn position_self_edges(g: &mut Graph) { + for v in g.nodes() { + let node = g.node(&v).cloned().unwrap(); + if node.dummy.unwrap_or("".to_string()) == "selfedge" { + let self_node = g.node(&node.e.as_ref().unwrap().v).unwrap(); + let x = self_node.x + self_node.width / 2.0; + let y = self_node.y; + let dx = node.x - x; + let dy = self_node.height / 2.0; + let mut graph_edge = node.label.clone().unwrap(); + graph_edge.points = Some(vec![ + GraphEdgePoint { + x: x + 2.0 * dx / 3.0, + y: y - dy, + }, + GraphEdgePoint { + x: x + 2.0 * dx / 3.0, + y: y - dy, + }, + GraphEdgePoint { + x: x + 5.0 * dx / 6.0, + y: y - dy, + }, + GraphEdgePoint { x: x + dx, y }, + GraphEdgePoint { + x: x + 5.0 * dx / 6.0, + y: y + dy, + }, + GraphEdgePoint { + x: x + 2.0 * dx / 3.0, + y: y + dy, + }, + ]); + graph_edge.x = node.x; + graph_edge.y = node.y; + let _ = g.set_edge_with_obj(&node.e.unwrap(), Some(graph_edge)); + g.remove_node(&v); + } + } +} + +pub fn run_layout(graph: &mut Graph) { + make_space_for_edge_labels(graph); + remove_self_edges(graph); + acyclic::run(graph); + nesting_graph::run(graph); + // calculating ranks + let mut nc_graph: Graph = as_non_compound_graph(graph); + rank(&mut nc_graph); + transfer_node_edge_labels(&nc_graph, graph); + // done with calculating ranks + inject_edge_label_proxies(graph); + remove_empty_ranks(graph); + nesting_graph::cleanup(graph); + normalize_ranks(graph); + assign_rank_min_max(graph); + remove_edge_label_proxies(graph); + normalize::run(graph); + parent_dummy_chains(graph); + add_border_segments(graph); + order(graph); + insert_self_edges(graph); + coordinate_system::adjust(graph); + position::position(graph); + position_self_edges(graph); + remove_border_nodes(graph); + normalize::undo(graph); + fixup_edge_label_coords(graph); + coordinate_system::undo(graph); + translate_graph(graph); + assign_node_intersects(graph); + reverse_points_for_reversed_edges(graph); + acyclic::undo(graph); +} diff --git a/patches/dagre_rust/src/layout/nesting_graph.rs b/patches/dagre_rust/src/layout/nesting_graph.rs new file mode 100644 index 0000000..83f39cb --- /dev/null +++ b/patches/dagre_rust/src/layout/nesting_graph.rs @@ -0,0 +1,213 @@ +use crate::layout::{util, GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::graph::GRAPH_NODE; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +/* + * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs, + * adds appropriate edges to ensure that all cluster nodes are placed between + * these boundries, and ensures that the graph is connected. + * + * In addition we ensure, through the use of the minlen property, that nodes + * and subgraph border nodes to not end up on the same rank. + * + * Preconditions: + * + * 1. Input graph is a DAG + * 2. Nodes in the input graph has a minlen attribute + * + * Postconditions: + * + * 1. Input graph is connected. + * 2. Dummy nodes are added for the tops and bottoms of subgraphs. + * 3. The minlen attribute for nodes is adjusted to ensure nodes do not + * get placed on the same rank as subgraph border nodes. + * + * The nesting graph idea comes from Sander, "Layout of Compound Directed + * Graphs." + */ +pub fn run(graph: &mut Graph) { + let graph_node = GraphNode::default(); + let root = util::add_dummy_node(graph, "root".to_string(), graph_node, "_root".to_string()); + let depths = tree_depths(graph); + let mut height: usize = 0; + for depth in depths.values() { + if depth > &height { + height = depth.to_owned(); + } + } + if height > 0 { + height -= 1; + } + + let node_sep = (2 * height + 1) as f32; + graph.graph_mut().nesting_root = Some(root.clone()); + + // Multiply minlen by nodeSep to align nodes on non-border ranks. + let edge_objs = graph.edges(); + for edge_obj in edge_objs.into_iter() { + let _edge_label = graph.edge_mut_with_obj(&edge_obj); + if _edge_label.is_none() { + continue; + } + let edge_label = _edge_label.unwrap(); + edge_label.minlen = Some(edge_label.minlen.unwrap_or(1.0) * node_sep); + } + + // Calculate a weight that is sufficient to keep subgraphs vertically compact + let weight = sum_weights(graph) + 1.0; + + // Create border nodes and link them up + let children = graph.children(&GRAPH_NODE.to_string()); + for child_id in children.into_iter() { + dfs( + graph, &root, &node_sep, &weight, &height, &depths, &child_id, + ); + } + + // Save the multiplier for node layers for later removal of empty border + // layers. + graph.graph_mut().node_rank_factor = Some(node_sep); +} + +fn tree_depths(graph: &Graph) -> OrderedHashMap { + let mut depths: OrderedHashMap = OrderedHashMap::new(); + + fn dfs( + node_id: String, + depth: usize, + depths: &mut OrderedHashMap, + graph: &Graph, + ) { + let children = graph.children(&node_id); + for child_id in children.iter() { + // recursion for child node ids + dfs(child_id.clone(), depth + 1, depths, graph); + } + // setting for current node + depths.insert(node_id.clone(), depth); + } + + // processing root nodes + for node_id in graph.children(&GRAPH_NODE.to_string()) { + dfs(node_id, 1, &mut depths, graph); + } + return depths; +} + +fn dfs( + graph: &mut Graph, + root: &String, + node_sep: &f32, + weight: &f32, + height: &usize, + depths: &OrderedHashMap, + node_id: &String, +) { + let children = graph.children(node_id); + if children.len() == 0 { + if node_id != root { + let mut graph_edge = GraphEdge::default(); + graph_edge.minlen = Some(node_sep.clone()); + graph_edge.weight = Some(0.0); + let _ = graph.set_edge(&root, &node_id, Some(graph_edge), None); + } + return (); + } + + let top = util::add_border_node(graph, "_bt", None, None); + let bottom = util::add_border_node(graph, "_bb", None, None); + + let _pt = graph.set_parent(&top, Some(node_id.clone())); + let _pb = graph.set_parent(&bottom, Some(node_id.clone())); + + let _label = graph.node_mut(node_id); + if let Some(label) = _label { + label.border_top = Some(top.clone()); + label.border_bottom = Some(bottom.clone()); + } + + for child_id in children.into_iter() { + dfs(graph, root, node_sep, weight, height, depths, &child_id); + + let _child_node = graph.node(&child_id); + if _child_node.is_none() { + continue; + } + + let child_node = _child_node.unwrap(); + let border_top = child_node.border_top.clone(); + let border_bottom = child_node.border_bottom.clone(); + + let mut child_top = child_id.clone(); + let mut child_bottom = child_id.clone(); + let mut this_weight: f32 = weight.clone(); + let mut minlen: usize = 1; + + if border_top.is_some() { + child_top = border_top.clone().unwrap(); + } + if border_bottom.is_some() { + child_bottom = border_bottom.unwrap(); + } + if border_top.is_some() { + this_weight = 2.0 * weight.clone(); + } + if child_top == child_bottom { + minlen = height - depths.get(node_id).cloned().unwrap_or(0) + 1; + } + + let mut _ct_graph_edge = GraphEdge::default(); + _ct_graph_edge.minlen = Some(minlen.clone() as f32); + _ct_graph_edge.weight = Some(this_weight.clone()); + _ct_graph_edge.nesting_edge = Some(true); + let _ct = graph.set_edge(&top, &child_top, Some(_ct_graph_edge), None); + + let mut _cb_graph_edge = GraphEdge::default(); + _cb_graph_edge.minlen = Some(minlen.clone() as f32); + _cb_graph_edge.weight = Some(this_weight.clone()); + _cb_graph_edge.nesting_edge = Some(true); + let _cb = graph.set_edge(&child_bottom, &bottom, Some(_cb_graph_edge), None); + } + + if graph.parent(node_id).is_none() { + let mut graph_edge = GraphEdge::default(); + graph_edge.minlen = + Some((depths.get(node_id).cloned().unwrap_or(0) + height.clone()) as f32); + graph_edge.weight = Some(0.0); + graph_edge.nesting_edge = Some(true); + let _ = graph.set_edge(&root, &top, Some(graph_edge), None); + } +} + +fn sum_weights(graph: &Graph) -> f32 { + let mut total_weights: f32 = 0.0; + + for edge in graph.edges() { + if let Some(edge_label) = graph.edge_with_obj(&edge) { + if let Some(weight) = edge_label.weight { + total_weights += weight; + } + } + } + + return total_weights; +} + +pub fn cleanup(graph: &mut Graph) { + let graph_label = graph.graph(); + if graph_label.nesting_root.is_some() { + graph.remove_node(&graph_label.nesting_root.clone().unwrap()); + } + graph.graph_mut().nesting_root = None; + // removing nesting edge + let edges = graph.edges(); + for edge in edges.into_iter() { + let _edge_label = graph.edge_with_obj(&edge); + if let Some(edge_label) = _edge_label { + if edge_label.nesting_edge.clone().unwrap_or(false) { + graph.remove_edge_with_obj(&edge); + } + } + } +} diff --git a/patches/dagre_rust/src/layout/normalize/mod.rs b/patches/dagre_rust/src/layout/normalize/mod.rs new file mode 100644 index 0000000..50c9ed6 --- /dev/null +++ b/patches/dagre_rust/src/layout/normalize/mod.rs @@ -0,0 +1,132 @@ +use crate::layout::util::add_dummy_node; +use crate::{GraphConfig, GraphEdge, GraphEdgePoint, GraphNode}; +use graphlib_rust::{Edge, Graph}; +/* + * Breaks any long edges in the graph into short segments that span 1 layer + * each. This operation is undoable with the denormalize function. + * + * Pre-conditions: + * + * 1. The input graph is a DAG. + * 2. Each node in the graph has a "rank" property. + * + * Post-condition: + * + * 1. All edges in the graph have a length of 1. + * 2. Dummy nodes are added where edges have been split into segments. + * 3. The graph is augmented with a "dummyChains" attribute which contains + * the first dummy in each chain of dummy nodes produced. + */ + +pub fn run(g: &mut Graph) { + g.graph_mut().dummy_chains = Some(vec![]); + let edges = g.edges(); + for edge_obj in edges.into_iter() { + normalize_edge(g, &edge_obj); + } +} + +fn normalize_edge(g: &mut Graph, e: &Edge) { + let mut v = e.v.clone(); + let w = e.w.clone(); + let mut v_rank = g + .node(&v) + .unwrap_or(&GraphNode::default()) + .rank + .clone() + .unwrap_or(0); + let w_rank = g + .node(&w) + .unwrap_or(&GraphNode::default()) + .rank + .clone() + .unwrap_or(0); + // let name = e.name.clone(); // TODO: it was creating error for multi-graph option + let edge_label = g.edge_mut_with_obj(&e).unwrap(); + edge_label.points = Some(vec![]); + let weight = edge_label.weight.clone(); + let label_rank = edge_label.label_rank.unwrap_or(0); + + if w_rank == v_rank + 1 { + return (); + } + + let _edge_label = edge_label.clone(); + g.remove_edge_with_obj(&e); + + let mut i = 0; + v_rank += 1; + while v_rank < w_rank { + let mut attrs = GraphNode::default(); + attrs.edge_label = Some(_edge_label.clone()); + attrs.edge_obj = Some(e.clone()); + attrs.rank = Some(v_rank.clone()); + if v_rank == label_rank { + attrs.width = _edge_label.width.clone().unwrap_or(0.0); + attrs.height = _edge_label.height.clone().unwrap_or(0.0); + attrs.dummy = Some("edge-label".to_string()); + attrs.labelpos = _edge_label.labelpos.clone(); + } + let dummy = add_dummy_node(g, "edge".to_string(), attrs, "_d".to_string()); + let mut dummy_edge_label = GraphEdge::default(); + dummy_edge_label.weight = weight.clone(); + let _ = g.set_edge(&v, &dummy, Some(dummy_edge_label), None); // remove name from here + if i == 0 { + let graph_label = g.graph_mut(); + if graph_label.dummy_chains.is_none() { + graph_label.dummy_chains = Some(vec![]); + } + let dummy_chains = graph_label.dummy_chains.as_mut().unwrap(); + dummy_chains.push(dummy.clone()); + } + v = dummy.clone(); + i += 1; + v_rank += 1; + } + + let mut graph_edge = GraphEdge::default(); + graph_edge.weight = weight; + let _ = g.set_edge( + &v, + &w, + Some(graph_edge), + None, // removed name from here + ); +} + +pub fn undo(g: &mut Graph) { + if g.graph().dummy_chains.is_none() { + return (); + } + let dummy_chains = g.graph().dummy_chains.clone().unwrap(); + for v_ in dummy_chains.iter() { + let node_ = g.node(v_); + if node_.is_none() { + continue; + } + let mut node = node_.cloned().unwrap(); + let mut orig_label = node.edge_label.clone().unwrap_or(GraphEdge::default()); + let edge_obj = node.edge_obj.unwrap(); + let mut v = v_.clone(); + while node.dummy.is_some() { + let sucs = g.successors(&v).unwrap_or(vec![]); + let default_w = "".to_string(); + let w = sucs.first().unwrap_or(&default_w); + g.remove_node(&v); + let points = orig_label.points.as_mut().unwrap(); + points.push(GraphEdgePoint { + x: node.x.clone(), + y: node.y.clone(), + }); + if node.dummy.as_ref().unwrap() == "edge-label" { + orig_label.x = node.x.clone(); + orig_label.y = node.y.clone(); + orig_label.width = Some(node.width.clone()); + orig_label.height = Some(node.height.clone()); + } + v = w.clone(); + node = g.node(&v).cloned().unwrap(); + } + let _ = g.set_edge_with_obj(&edge_obj, Some(orig_label)); + } +} diff --git a/patches/dagre_rust/src/layout/order/add_subgraph_constraints.rs b/patches/dagre_rust/src/layout/order/add_subgraph_constraints.rs new file mode 100644 index 0000000..26f6f18 --- /dev/null +++ b/patches/dagre_rust/src/layout/order/add_subgraph_constraints.rs @@ -0,0 +1,38 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +pub fn add_subgraph_constraints( + g: &Graph, + cg: &mut Graph, + vs: &Vec, +) { + let mut prev: OrderedHashMap = OrderedHashMap::new(); + let mut _root_prev: Option = None; + + vs.iter().for_each(|v| { + let mut child = g.parent(v).cloned(); + let mut _parent: Option = None; + let mut _prev_child: Option = None; + while child.is_some() { + _parent = g.parent(&child.clone().unwrap()).cloned(); + if _parent.is_some() { + _prev_child = prev + .get(&_parent.clone().unwrap_or("".to_string())) + .cloned(); + prev.insert(_parent.clone().unwrap(), child.clone().unwrap()); + } else { + _prev_child = _root_prev.clone(); + _root_prev = child.clone(); + } + + let prev_child = _prev_child.clone().unwrap_or("".to_string()); + let child_ = child.clone().unwrap_or("".to_string()); + if _prev_child.is_some() && prev_child != child_ { + let _ = cg.set_edge(&prev_child, &child_, None, None); + return (); + } + child = _parent.clone(); + } + }); +} diff --git a/patches/dagre_rust/src/layout/order/barycenter.rs b/patches/dagre_rust/src/layout/order/barycenter.rs new file mode 100644 index 0000000..81e4a6d --- /dev/null +++ b/patches/dagre_rust/src/layout/order/barycenter.rs @@ -0,0 +1,44 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; + +#[derive(Debug, Clone)] +pub struct Barycenter { + pub v: String, + pub barycenter: Option, + pub weight: Option, +} + +pub fn barycenter( + g: &Graph, + movable: &Vec, +) -> Vec { + movable + .iter() + .map(|v| { + let in_v = g.in_edges(v, None).unwrap_or(vec![]); + if in_v.len() == 0 { + return Barycenter { + v: v.clone(), + barycenter: None, + weight: None, + }; + } + + //( sum, weight ) + let mut result = (0, 0); + in_v.iter().for_each(|e| { + let edge = g.edge_with_obj(&e).unwrap(); + let node_u = g.node(&e.v).unwrap(); + let edge_weight = edge.weight.clone().unwrap_or(0.0) as i32; + result.0 += edge_weight * (node_u.order.clone().unwrap_or(0) as i32); + result.1 += edge_weight; + }); + + return Barycenter { + v: v.clone(), + barycenter: Some(result.0.clone() as f32 / result.1.clone() as f32), + weight: Some(result.1 as f32), + }; + }) + .collect() +} diff --git a/patches/dagre_rust/src/layout/order/build_layer_graph.rs b/patches/dagre_rust/src/layout/order/build_layer_graph.rs new file mode 100644 index 0000000..1f952bd --- /dev/null +++ b/patches/dagre_rust/src/layout/order/build_layer_graph.rs @@ -0,0 +1,112 @@ +use crate::layout::util::unique_id; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::{Edge, Graph, GraphOption}; + +/* + * Constructs a graph that can be used to sort a layer of nodes. The graph will + * contain all base and subgraph nodes from the request layer in their original + * hierarchy and any edges that are incident on these nodes and are of the type + * requested by the "relationship" parameter. + * + * Nodes from the requested rank that do not have parents are assigned a root + * node in the output graph, which is set in the root graph attribute. This + * makes it easy to walk the hierarchy of movable nodes during ordering. + * + * Pre-conditions: + * + * 1. Input graph is a DAG + * 2. Base nodes in the input graph have a rank attribute + * 3. Subgraph nodes in the input graph has minRank and maxRank attributes + * 4. Edges have an assigned weight + * + * Post-conditions: + * + * 1. Output graph has all nodes in the movable rank with preserved + * hierarchy. + * 2. Root nodes in the movable layer are made children of the node + * indicated by the root attribute of the graph. + * 3. Non-movable nodes incident on movable nodes, selected by the + * relationship parameter, are included in the graph (without hierarchy). + * 4. Edges incident on movable nodes, selected by the relationship + * parameter, are added to the output graph. + * 5. The weights for copied edges are aggregated as need, since the output + * graph is not a multi-graph. + */ + +#[derive(Debug, Copy, Clone)] +pub enum GraphRelationship { + InEdges, + OutEdges, +} + +pub fn build_layer_graph( + g: &mut Graph, + rank: &i32, + relationship: GraphRelationship, +) -> Graph { + let root = create_root_node(g); + let mut result: Graph = Graph::new(Some(GraphOption { + directed: Some(true), + compound: Some(true), + multigraph: None, + })); + let graph_label = result.graph_mut(); + graph_label.root = Some(root.clone()); + + g.nodes().iter().for_each(|v| { + let node = g.node(v).unwrap(); + let parent = g.parent(v); + + let node_rank = node.rank.clone().unwrap_or(0); + let node_min_rank = node.min_rank.unwrap_or(0); + let node_max_rank = node.max_rank.unwrap_or(0); + let mut _relationship: Vec = g.in_edges(v, None).unwrap_or(vec![]); + match relationship { + GraphRelationship::OutEdges => { + _relationship = g.out_edges(v, None).unwrap_or(vec![]); + } + _ => (), + } + if &node_rank == rank || &node_min_rank <= rank && rank <= &node_max_rank { + result.set_node(v.clone(), Some(node.clone())); + if parent.is_some() { + let _ = result.set_parent(v, parent.cloned()); + } else { + let _ = result.set_parent(v, Some(root.clone())); + } + + // This assumes we have only short edges! + _relationship.iter().for_each(|e| { + let u = if &e.v == v { e.w.clone() } else { e.v.clone() }; + let edge = result.edge(&u, &v, None); + let weight = if edge.is_some() { + edge.unwrap().weight.clone().unwrap_or(0.0) + } else { + 0.0 + }; + let mut edge_label = GraphEdge::default(); + edge_label.weight = + Some(g.edge_with_obj(&e).unwrap().weight.clone().unwrap_or(0.0) + weight); + let _ = result.set_edge(&u, &v, Some(edge_label), None); + }); + + if node.min_rank.is_some() { + let mut graph_node = GraphNode::default(); + graph_node.border_left_ = node.border_left.as_ref().unwrap().get(rank).cloned(); + graph_node.border_right_ = node.border_right.as_ref().unwrap().get(rank).cloned(); + result.set_node(v.clone(), Some(graph_node)); + } + } + }); + + result +} + +pub fn create_root_node(g: &Graph) -> String { + let mut v = format!("_root{}", unique_id()); + while g.has_node(&v) { + v = format!("_root{}", unique_id()); + } + + v +} diff --git a/patches/dagre_rust/src/layout/order/cross_count.rs b/patches/dagre_rust/src/layout/order/cross_count.rs new file mode 100644 index 0000000..f9ded25 --- /dev/null +++ b/patches/dagre_rust/src/layout/order/cross_count.rs @@ -0,0 +1,104 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +/* + * A function that takes a layering (an array of layers, each with an array of + * ordererd nodes) and a graph and returns a weighted crossing count. + * + * Pre-conditions: + * + * 1. Input graph must be simple (not a multigraph), directed, and include + * only simple edges. + * 2. Edges in the input graph must have assigned weights. + * + * Post-conditions: + * + * 1. The graph and layering matrix are left unchanged. + * + * This algorithm is derived from Barth, et al., "Bilayer Cross Counting." + */ + +pub fn cross_count( + g: &mut Graph, + layering: &mut Vec>, +) -> usize { + let mut cc = 0; + let mut i = 1; + while i < layering.len() { + cc += two_layer_cross_count(g, layering, &(i - 1), &i); + i += 1; + } + + return cc; +} + +pub fn two_layer_cross_count( + g: &mut Graph, + layering: &mut Vec>, + north_idx: &usize, + south_idx: &usize, +) -> usize { + // Sort all of the edges between the north and south layers by their position + // in the north layer and then the south. Map these edges to the position of + // their head in the south layer. + let mut south_pos: OrderedHashMap = OrderedHashMap::new(); + let south_layer = layering.get(south_idx.clone()).cloned().unwrap_or(vec![]); + south_layer.iter().enumerate().for_each(|(idx, val)| { + south_pos.insert(val.clone(), idx); + }); + + // (pos, weight) + let south_entries: Vec<(usize, f32)> = layering + .get(north_idx.clone()) + .cloned() + .unwrap_or(vec![]) + .into_iter() + .map(|v| -> Vec<(usize, f32)> { + let mut out_edges: Vec<(usize, f32)> = g + .out_edges(&v, None) + .unwrap_or(vec![]) + .into_iter() + .map(|e| { + let pos = south_pos.get(&e.w).cloned().unwrap_or(0); + let label = g.edge_with_obj(&e).cloned().unwrap(); + (pos, label.weight.unwrap_or(0.0)) + }) + .collect(); + out_edges.sort_by(|e1, e2| e1.0.cmp(&e2.0)); + + out_edges + }) + .collect::>>() + .concat(); + + // Build the accumulator tree + let mut first_index = 1; + while first_index < south_layer.len() { + first_index <<= 1; + } + + let tree_size = 2 * first_index - 1; + first_index -= 1; + + let mut tree: Vec = vec![0; tree_size]; + + // Calculate the weighted crossings + let mut cc = 0; + south_entries.iter().for_each(|entry| { + let mut idx = entry.0 + first_index; + tree.insert(idx, tree[idx] + (entry.1 as usize)); + + let mut weight_sum: usize = 0; + while idx > 0 { + if idx % 2 != 0 { + weight_sum += tree[idx + 1]; + } + idx = (idx - 1) >> 1; + tree[idx] += entry.1 as usize; + } + cc += entry.1 as usize * weight_sum; + }); + + cc +} diff --git a/patches/dagre_rust/src/layout/order/init_order.rs b/patches/dagre_rust/src/layout/order/init_order.rs new file mode 100644 index 0000000..ffa9c0b --- /dev/null +++ b/patches/dagre_rust/src/layout/order/init_order.rs @@ -0,0 +1,68 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +/* + * Assigns an initial order value for each node by performing a DFS search + * starting from nodes in the first rank. Nodes are assigned an order in their + * rank as they are first visited. + * + * This approach comes from Gansner, et al., "A Technique for Drawing Directed + * Graphs." + * + * Returns a layering matrix with an array per layer and each layer sorted by + * the order of its nodes. + */ + +pub fn init_order(g: &Graph) -> Vec> { + let mut visited: OrderedHashMap = OrderedHashMap::new(); + let mut simple_nodes: Vec = g + .nodes() + .into_iter() + .filter(|v| g.children(v).len() == 0) + .collect(); + let max_rank = simple_nodes + .iter() + .map(|v| g.node(v).unwrap().rank.clone().unwrap_or(0)) + .max() + .unwrap_or(0); + let mut layers: Vec> = (0..=max_rank).map(|_| -> Vec { vec![] }).collect(); + + fn dfs( + v: &String, + g: &Graph, + visited: &mut OrderedHashMap, + layers: &mut Vec>, + ) { + if visited.contains_key(v) { + return (); + } + + visited.insert(v.clone(), true); + let node = g.node(v).unwrap(); + let node_rank = node.rank.unwrap_or(0) as usize; + if layers.get(node_rank.clone()).is_none() { + layers.insert(node_rank.clone(), vec![]); + } + let layer: &mut Vec = layers.get_mut(node_rank.clone()).unwrap(); + layer.push(v.clone()); + + let sucs = g.successors(v).unwrap_or(vec![]); + for sv in sucs.iter() { + dfs(sv, g, visited, layers) + } + } + + simple_nodes.sort_by(|v1, v2| { + let v1_rank = g.node(v1).unwrap().rank.clone().unwrap_or(0); + let v2_rank = g.node(v2).unwrap().rank.clone().unwrap_or(0); + + v1_rank.cmp(&v2_rank) + }); + + for v in simple_nodes.iter() { + dfs(v, g, &mut visited, &mut layers); + } + + return layers; +} diff --git a/patches/dagre_rust/src/layout/order/mod.rs b/patches/dagre_rust/src/layout/order/mod.rs new file mode 100644 index 0000000..dccab2c --- /dev/null +++ b/patches/dagre_rust/src/layout/order/mod.rs @@ -0,0 +1,110 @@ +pub mod add_subgraph_constraints; +pub mod barycenter; +pub mod build_layer_graph; +pub mod cross_count; +pub mod init_order; +pub mod resolve_conflicts; +pub mod sort; +pub mod sort_subgraph; + +use crate::layout::order::add_subgraph_constraints::add_subgraph_constraints; +use crate::layout::order::build_layer_graph::{build_layer_graph, GraphRelationship}; +use crate::layout::order::cross_count::cross_count; +use crate::layout::order::init_order::init_order; +use crate::layout::order::sort_subgraph::sort_subgraph; +use crate::layout::util; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::graph::GRAPH_NODE; +use graphlib_rust::Graph; + +/* + * Applies heuristics to minimize edge crossings in the graph and sets the best + * order solution as an order attribute on each node. + * + * Pre-conditions: + * + * 1. Graph must be DAG + * 2. Graph nodes must be objects with a "rank" attribute + * 3. Graph edges must have the "weight" attribute + * + * Post-conditions: + * + * 1. Graph nodes will have an "order" attribute based on the results of the + * algorithm. + */ + +pub fn order(g: &mut Graph) { + let max_rank = util::max_rank(g); + let down_layer_ranks: Vec = (1..=max_rank).collect(); + let up_layer_ranks: Vec = (0..max_rank).rev().collect(); + + let mut down_layer_graphs = + build_layer_graphs(g, &down_layer_ranks, GraphRelationship::InEdges); + let mut up_layer_graphs = build_layer_graphs(g, &up_layer_ranks, GraphRelationship::OutEdges); + + let mut layering = init_order(g); + assign_order(g, &layering); + + let mut best_cc = f64::INFINITY; + let mut best: Vec> = Vec::new(); + + let mut i = 0; + let mut last_best = 0; + while last_best < 4 { + let _layer_graphs = if i % 2 != 0 { + &mut down_layer_graphs + } else { + &mut up_layer_graphs + }; + + sweep_layer_graphs(_layer_graphs, i % 4 >= 2); + + layering = util::build_layer_matrix(g); + let cc = cross_count(g, &mut layering) as f64; + if cc < best_cc { + last_best = 0; + best = layering.clone(); + best_cc = cc; + } + + last_best += 1; + i += 1; + } + + assign_order(g, &best); +} + +fn build_layer_graphs( + g: &mut Graph, + ranks: &Vec, + relationship: GraphRelationship, +) -> Vec> { + return ranks + .iter() + .map(|rank| build_layer_graph(g, rank, relationship)) + .collect(); +} + +fn sweep_layer_graphs( + layer_graphs: &mut Vec>, + bias_right: bool, +) { + let mut cg: Graph = Graph::new(None); + layer_graphs.iter_mut().for_each(|lg| { + let root = lg.graph().root.clone().unwrap_or(GRAPH_NODE.to_string()); + let sorted = sort_subgraph(lg, &root, &cg, &bias_right); + sorted.vs.iter().enumerate().for_each(|(i, v)| { + lg.node_mut(v).unwrap().order = Some(i); + }); + add_subgraph_constraints(lg, &mut cg, &sorted.vs); + }) +} + +fn assign_order(g: &mut Graph, layering: &Vec>) { + for layer in layering { + for (i, v) in layer.iter().enumerate() { + let node_label = g.node_mut(v).unwrap(); + node_label.order = Some(i); + } + } +} diff --git a/patches/dagre_rust/src/layout/order/resolve_conflicts.rs b/patches/dagre_rust/src/layout/order/resolve_conflicts.rs new file mode 100644 index 0000000..c45a79c --- /dev/null +++ b/patches/dagre_rust/src/layout/order/resolve_conflicts.rs @@ -0,0 +1,188 @@ +/* + * Given a list of entries of the form {v, barycenter, weight} and a + * constraint graph this function will resolve any conflicts between the + * constraint graph and the barycenters for the entries. If the barycenters for + * an entry would violate a constraint in the constraint graph then we coalesce + * the nodes in the conflict into a new node that respects the contraint and + * aggregates barycenter and weight information. + * + * This implementation is based on the description in Forster, "A Fast and + * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it + * differs in some specific details. + * + * Pre-conditions: + * + * 1. Each entry has the form {v, barycenter, weight}, or if the node has + * no barycenter, then {v}. + * + * Returns: + * + * A new list of entries of the form {vs, i, barycenter, weight}. The list + * `vs` may either be a singleton or it may be an aggregation of nodes + * ordered such that they do not violate constraints from the constraint + * graph. The property `i` is the lowest original index of any of the + * elements in `vs`. + */ +use crate::layout::order::barycenter::Barycenter; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +#[derive(Debug, Clone)] +pub struct ResolvedBaryEntry { + pub indegree: i32, + pub _in: Vec, + pub _out: Vec, + pub vs: Vec, + pub i: usize, + pub barycenter: Option, + pub weight: Option, + pub merged: Option, +} + +pub fn resolve_conflicts( + entries: &Vec, + cg: &Graph, +) -> Vec { + let mut mapped_entries: OrderedHashMap = OrderedHashMap::new(); + for (i, entry) in entries.iter().enumerate() { + let mut mapped_entry = ResolvedBaryEntry { + indegree: 0, + _in: vec![], + _out: vec![], + vs: vec![entry.v.clone()], + i, + barycenter: None, + weight: None, + merged: None, + }; + if mapped_entry.barycenter.is_some() { + mapped_entry.barycenter = entry.barycenter.clone(); + mapped_entry.weight = entry.weight.clone(); + } + mapped_entries.insert(entry.v.clone(), mapped_entry); + } + + cg.edges().iter().for_each(|e| { + let entry_v_ = mapped_entries.get(&e.v); + let entry_w_ = mapped_entries.get(&e.w).cloned(); + if entry_v_.is_some() && entry_w_.is_some() { + if let Some(entry_w) = mapped_entries.get_mut(&e.w) { + entry_w.indegree += 1; + } + if let Some(entry_v) = mapped_entries.get_mut(&e.v) { + entry_v._out.push(entry_w_.clone().unwrap()); + } + } + }); + + let mut source_set: Vec = mapped_entries + .values() + .filter(|entry| entry.indegree == 0) + .cloned() + .collect(); + + return do_resolve_conflicts(&mut source_set); +} + +fn do_resolve_conflicts(source_set: &mut Vec) -> Vec { + let mut entries: Vec = vec![]; + let mut source_hash: OrderedHashMap = OrderedHashMap::new(); + + source_set.iter().for_each(|entry| { + source_hash.insert(entry.i.clone(), entry.clone()); + }); + + fn handle_in( + v_idx: &usize, + u_idx: &usize, + source_hash: &mut OrderedHashMap, + ) { + let v_entry = source_hash.get(v_idx).unwrap(); + let u_entry = source_hash.get(u_idx).unwrap(); + if u_entry.merged.is_some() { + return (); + } + + if u_entry.barycenter.is_none() + || v_entry.barycenter.is_none() + || u_entry.barycenter.clone().unwrap_or(0.0) + >= v_entry.barycenter.clone().unwrap_or(0.0) + { + merge_entries(v_idx, u_idx, source_hash); + } + } + + fn handle_out( + v_idx: &usize, + w_idx: &usize, + source_hash: &mut OrderedHashMap, + source_set: &mut Vec, + ) { + let v_entry = source_hash.get(v_idx).cloned().unwrap(); + let w_entry = source_hash.get_mut(w_idx).unwrap(); + w_entry._in.push(v_entry); + w_entry.indegree -= 1; + if w_entry.indegree == 0 { + source_set.push(w_entry.clone()); + } + } + + while source_set.len() > 0 { + let entry_ = source_set.pop().unwrap(); + let entry = source_hash.get(&entry_.i).unwrap(); + + let _in = entry._in.clone(); + let out = entry_._out.clone(); + _in.iter().rev().for_each(|u_entry| { + handle_in(&entry_.i, &u_entry.i, &mut source_hash); + }); + out.iter().for_each(|w_entry| { + handle_out(&entry_.i, &w_entry.i, &mut source_hash, source_set); + }); + + entries.push(source_hash.get(&entry_.i).cloned().unwrap()); + } + + return entries + .into_iter() + .filter(|entry| !entry.merged.clone().unwrap_or(false)) + .collect(); +} + +fn merge_entries( + target_idx: &usize, + source_idx: &usize, + source_hash: &mut OrderedHashMap, +) { + let mut sum = 0.0; + let mut weight = 0.0; + + let target_ = source_hash.get(target_idx).cloned().unwrap(); + if target_.weight.is_some() { + let target_weight = target_.weight.clone().unwrap_or(0.0); + let target_barycenter = target_.barycenter.clone().unwrap_or(0.0); + sum += target_barycenter * target_weight; + weight += target_weight; + } + + let source_ = source_hash.get(source_idx).cloned().unwrap(); + if source_.weight.is_some() { + let source_weight = source_.weight.clone().unwrap_or(0.0); + let source_barycenter = source_.barycenter.clone().unwrap_or(0.0); + sum += source_barycenter * source_weight; + weight += source_weight; + } + + let mut target_vs = source_.vs.clone(); + target_vs.append(&mut target_.vs.clone()); + + let target = source_hash.get_mut(target_idx).unwrap(); + target.vs = target_vs; + target.barycenter = Some(sum / weight); + target.weight = Some(weight); + target.i = std::cmp::min(source_.i.clone(), target_.i.clone()); + + let source = source_hash.get_mut(source_idx).unwrap(); + source.merged = Some(true); +} diff --git a/patches/dagre_rust/src/layout/order/sort.rs b/patches/dagre_rust/src/layout/order/sort.rs new file mode 100644 index 0000000..601a30a --- /dev/null +++ b/patches/dagre_rust/src/layout/order/sort.rs @@ -0,0 +1,78 @@ +use crate::layout::order::resolve_conflicts::ResolvedBaryEntry; +use crate::layout::order::sort_subgraph::SubgraphResult; +use crate::layout::util; +use crate::layout::util::PartitionResponse; +use std::cmp::Ordering; + +pub fn sort(entries: &Vec, bias_right: &bool) -> SubgraphResult { + let parts: PartitionResponse = util::partition( + entries, + Box::new(|val: &ResolvedBaryEntry| -> bool { val.barycenter.is_some() }), + ); + + let mut sortable = parts.lhs.clone(); + sortable.sort_by(|e1, e2| compare_with_bias(e1, e2, bias_right)); + let mut unsortable = parts.rhs.clone(); + unsortable.sort_by(|e1, e2| e2.i.cmp(&e1.i)); + + let mut vs: Vec = vec![]; + let mut sum = 0.0; + let mut weight = 0.0; + let mut vs_index: usize = 0; + + vs_index = consume_unsortable(&mut vs, &mut sortable, &mut vs_index); + sortable.iter().for_each(|entry| { + vs_index += entry.vs.len(); + vs.append(entry.vs.clone().as_mut()); + let entry_weight = entry.weight.clone().unwrap_or(0.0); + sum += entry.barycenter.clone().unwrap_or(0.0) * entry_weight.clone(); + weight += entry_weight.clone(); + vs_index = consume_unsortable(&mut vs, &mut unsortable, &mut vs_index); + }); + + let mut result = SubgraphResult::default(); + result.vs = vs; + if weight > 0.0 { + result.barycenter = sum / weight; + result.weight = weight; + } + + return result; +} + +fn consume_unsortable( + vs: &mut Vec, + unsortable: &mut Vec, + index: &mut usize, +) -> usize { + if unsortable.len() == 0 { + return index.clone(); + } + let mut last: ResolvedBaryEntry = unsortable.get(unsortable.len() - 1).cloned().unwrap(); + while unsortable.len() > 0 && &last.i <= index { + vs.append(last.vs.clone().as_mut()); + last = unsortable.pop().unwrap(); + *index += 1; + } + return index.clone(); +} + +fn compare_with_bias( + entry_v: &ResolvedBaryEntry, + entry_w: &ResolvedBaryEntry, + bias: &bool, +) -> Ordering { + let barycenter_v = entry_v.barycenter.clone().unwrap_or(0.0) as i32; + let barycenter_w = entry_w.barycenter.clone().unwrap_or(0.0) as i32; + if barycenter_v < barycenter_w { + return Ordering::Greater; + } else if barycenter_v > barycenter_w { + return Ordering::Less; + } + + return if !bias { + entry_v.i.cmp(&entry_w.i) + } else { + entry_w.i.cmp(&entry_v.i) + }; +} diff --git a/patches/dagre_rust/src/layout/order/sort_subgraph.rs b/patches/dagre_rust/src/layout/order/sort_subgraph.rs new file mode 100644 index 0000000..2440d2b --- /dev/null +++ b/patches/dagre_rust/src/layout/order/sort_subgraph.rs @@ -0,0 +1,118 @@ +use crate::layout::order::barycenter::{barycenter, Barycenter}; +use crate::layout::order::resolve_conflicts::{resolve_conflicts, ResolvedBaryEntry}; +use crate::layout::order::sort::sort; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +#[derive(Debug, Clone, Default)] +pub struct SubgraphResult { + pub vs: Vec, + pub barycenter: f32, + pub weight: f32, +} + +pub fn sort_subgraph( + g: &Graph, + v: &String, + cg: &Graph, + bias_right: &bool, +) -> SubgraphResult { + let mut movable = g.children(v); + let node = g.node(v); + let bl = if node.is_some() { + node.unwrap().border_left_.clone() + } else { + None + }; + let br = if node.is_some() { + node.unwrap().border_right_.clone() + } else { + None + }; + let mut subgraphs: OrderedHashMap = OrderedHashMap::new(); + + if br.is_some() { + movable = movable + .into_iter() + .filter(|w| w != &bl.clone().unwrap() && w != &br.clone().unwrap()) + .collect(); + } + + let mut barycenters = barycenter(g, &movable); + barycenters.iter_mut().for_each(|entry| { + if g.children(&entry.v).len() > 0 { + let subgraph_result = sort_subgraph(g, &entry.v, cg, bias_right); + subgraphs.insert(entry.v.clone(), subgraph_result.clone()); + merge_barycenters(entry, &subgraph_result); + } + }); + + let mut entries = resolve_conflicts(&barycenters, cg); + expand_subgraphs(&mut entries, &subgraphs); + + let mut result = sort(&entries, bias_right); + if bl.is_some() { + let bl_ = bl.clone().unwrap(); + let br_ = br.clone().unwrap(); + result.vs = vec![vec![bl_.clone()], result.vs.clone(), vec![br_.clone()]].concat(); + let bl_preds = g.predecessors(&bl_).unwrap_or(vec![]); + if bl_preds.len() > 0 { + let bl_pred = g.node(bl_preds.first().unwrap()).unwrap(); + let br_pred = g + .node(g.predecessors(&br_).unwrap_or(vec![]).first().unwrap()) + .unwrap(); + // not required as it was handled by structs' default + /* + if (!_.has(result, "barycenter")) { + result.barycenter = 0; + result.weight = 0; + } + */ + let bl_pred_order = bl_pred.order.clone().unwrap_or(0) as f32; + let br_pred_order = br_pred.order.clone().unwrap_or(0) as f32; + result.barycenter = (result.barycenter * result.weight + bl_pred_order + br_pred_order) + / (result.weight + 2.0); + result.weight += 2.0; + } + } + + return result; +} + +fn expand_subgraphs( + entries: &mut Vec, + subgraphs: &OrderedHashMap, +) { + entries.iter_mut().for_each(|entry| { + let mut vs: Vec = vec![]; + entry.vs.iter().for_each(|v| { + if subgraphs.contains_key(v) { + let subgraph = subgraphs.get(v).unwrap(); + subgraph.vs.iter().for_each(|v_| { + vs.push(v_.clone()); + }); + return (); + } + vs.push(v.clone()); + }); + + entry.vs = vs; + }); +} + +fn merge_barycenters(target: &mut Barycenter, other: &SubgraphResult) { + let target_barycenter = target.barycenter.clone().unwrap_or(0.0); + if target.barycenter.is_some() && target_barycenter > 0.0 { + let target_weight = target.weight.clone().unwrap_or(0.0); + + target.barycenter = Some( + (target_barycenter * target_weight + other.barycenter * other.weight) + / (target_weight + other.weight), + ); + target.weight = Some(target_weight + other.weight.clone()); + } else { + target.barycenter = Some(other.barycenter.clone()); + target.weight = Some(other.weight.clone()); + } +} diff --git a/patches/dagre_rust/src/layout/parent_dummy_chains.rs b/patches/dagre_rust/src/layout/parent_dummy_chains.rs new file mode 100644 index 0000000..3a40cb5 --- /dev/null +++ b/patches/dagre_rust/src/layout/parent_dummy_chains.rs @@ -0,0 +1,141 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::graph::GRAPH_NODE; +use graphlib_rust::Graph; +use ordered_hashmap::OrderedHashMap; + +pub fn parent_dummy_chains(g: &mut Graph) { + let post_order_nums: OrderedHashMap = postorder(g); + let dummy_chains = g.graph().dummy_chains.clone().unwrap_or(vec![]); + let empty_string = "".to_string(); + let empty_node = GraphNode::default(); + + for v_ in dummy_chains.iter() { + let mut v = v_.clone(); + let mut node = g.node(&v).unwrap(); + let edge_obj = node.edge_obj.clone().unwrap(); + let path_data = find_path(g, &post_order_nums, &edge_obj.v, &edge_obj.w); + let path = path_data.0; + let lca = &path_data.1; + let mut path_idx = 0; + // if path is empty + if path.len() == 0 { + continue; + } + let mut path_v = path.get(path_idx).unwrap(); + let mut ascending = true; + + while v != edge_obj.w { + node = g.node(&v).unwrap(); + + if ascending { + path_v = path.get(path_idx).unwrap_or(lca); + while path_v != lca + && g.node(path_v) + .unwrap_or(&empty_node) + .max_rank + .clone() + .unwrap_or(0) + < node.rank.clone().unwrap_or(0) + { + path_idx += 1; + if let Some(path_v_) = path.get(path_idx) { + path_v = path_v_; + } else { + break; + } + } + + if path_v == lca { + ascending = false + } + } + + if !ascending { + path_v = path.get(path_idx + 1).unwrap_or(&empty_string); + while path_idx < path.len() - 1 + && g.node(path_v).unwrap().min_rank.clone().unwrap_or(0) + <= node.rank.clone().unwrap_or(0) + { + path_idx += 1; + path_v = path.get(path_idx + 1).unwrap_or(&empty_string); + } + } + + let _ = g.set_parent(&v, Some(path_v.clone())); + v = g + .successors(&v) + .unwrap_or(vec![]) + .get(0) + .unwrap_or(&empty_string) + .clone(); + } + } +} + +// Find a path from v to w through the lowest common ancestor (LCA). Return the +// full path and the LCA. +fn find_path( + g: &Graph, + post_order_nums: &OrderedHashMap, + v: &String, + w: &String, +) -> (Vec, String) { + let mut v_path: Vec = vec![]; + let mut w_path: Vec = vec![]; + + let v_post_order_num = post_order_nums.get(v).cloned().unwrap_or((0, 0)); + let w_post_order_num = post_order_nums.get(w).cloned().unwrap_or((0, 0)); + let low = std::cmp::min(v_post_order_num.0, w_post_order_num.0); + let lim = std::cmp::min(v_post_order_num.1, w_post_order_num.1); + + // Traverse up from v to find the LCA + let mut _parent = v; + while let Some(parent) = g.parent(_parent) { + _parent = parent; + v_path.push(parent.clone()); + if let Some(post_order_num) = post_order_nums.get(parent) { + if post_order_num.0 <= low && lim <= post_order_num.1 { + break; + } + } else { + break; + } + } + + let lca = _parent; + // Traverse from w to LCA + let mut parent = g.parent(w).unwrap_or(lca); + while parent != lca { + w_path.push(parent.clone()); + parent = g.parent(parent).unwrap_or(lca); + } + + w_path.reverse(); + v_path.append(&mut w_path); + return (v_path, lca.clone()); +} + +fn postorder(g: &Graph) -> OrderedHashMap { + let mut result: OrderedHashMap = OrderedHashMap::new(); + let mut lim = 0; + + fn dfs( + v: &String, + g: &Graph, + lim: &mut i32, + result: &mut OrderedHashMap, + ) { + let low = lim.clone(); + g.children(&v).iter().for_each(|v_| { + dfs(v_, g, lim, result); + }); + result.insert(v.clone(), (low, lim.clone())); + *lim += 1; + } + + g.children(&GRAPH_NODE.to_string()).iter().for_each(|v| { + dfs(v, g, &mut lim, &mut result); + }); + + return result; +} diff --git a/patches/dagre_rust/src/layout/position/bk.rs b/patches/dagre_rust/src/layout/position/bk.rs new file mode 100644 index 0000000..f05a13d --- /dev/null +++ b/patches/dagre_rust/src/layout/position/bk.rs @@ -0,0 +1,718 @@ +/* + * This module provides coordinate assignment based on Brandes and Köpf, "Fast + * and Simple Horizontal Coordinate Assignment." + */ + +use crate::layout::add_border_segments::BorderTypeName; +use crate::layout::util; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::{Graph, GraphOption}; +use ordered_hashmap::OrderedHashMap; +use std::cmp::Ordering; + +/* + * Marks all edges in the graph with a type-1 conflict with the "type1Conflict" + * property. A type-1 conflict is one where a non-inner segment crosses an + * inner segment. An inner segment is an edge with both incident nodes marked + * with the "dummy" property. + * + * This algorithm scans layer by layer, starting with the second, for type-1 + * conflicts between the current layer and the previous layer. For each layer + * it scans the nodes from left to right until it reaches one that is incident + * on an inner segment. It then scans predecessors to determine if they have + * edges that cross that inner segment. At the end a final scan is done for all + * nodes on the current rank to see if they cross the last visited inner + * segment. + * + * This algorithm (safely) assumes that a dummy node will only be incident on a + * single node in the layers being scanned. + */ +fn find_type_1_conflicts( + g: &mut Graph, + layering: &Vec>, +) -> OrderedHashMap> { + let mut conflicts = OrderedHashMap::new(); + + fn visit_layer( + g: &mut Graph, + prev_layer: &Vec, + layer: &Vec, + conflicts: &mut OrderedHashMap>, + ) { + // last visited node in the previous layer that is incident on an inner + // segment. + let mut k0 = 0; + // Tracks the last node in this layer scanned for crossings with a type-1 + // segment. + let mut scan_pos = 0; + let prev_layer_length = prev_layer.len(); + let last_node = layer.last().unwrap().clone(); + + for (i, v) in layer.iter().enumerate() { + let w = find_other_inner_segment_node(g, v); + let k1 = if let Some(ref w) = w { + g.node(w).unwrap().order.unwrap_or(0) + } else { + prev_layer_length + }; + + if w.is_some() || *v == last_node { + for scan_node in layer[scan_pos..=i].iter() { + for u in g.predecessors(scan_node).unwrap() { + let u_label = g.node(&u).unwrap(); + let u_pos = u_label.order.unwrap_or(0); + if (u_pos < k0 || k1 < u_pos) + && !(u_label.dummy.is_some() + && g.node(scan_node).unwrap().dummy.is_some()) + { + add_conflict(conflicts, &u, scan_node); + } + } + } + scan_pos = i + 1; + k0 = k1; + } + } + } + + layering.iter().reduce(|prev_layer, layer| { + visit_layer(g, &prev_layer, layer, &mut conflicts); + + layer + }); + + conflicts +} + +pub fn find_type_2_conflicts( + g: &mut Graph, + layering: &Vec>, +) -> OrderedHashMap> { + let mut conflicts: OrderedHashMap> = OrderedHashMap::new(); + + fn scan( + g: &mut Graph, + south: &Vec, + south_pos: &usize, + south_end: &usize, + prev_north_border: &i32, + next_north_border: &i32, + conflicts: &mut OrderedHashMap>, + ) { + for i in south_pos.clone()..south_end.clone() { + let v: String = south.get(i).cloned().unwrap(); + if g.node(&v).is_some() && g.node(&v).unwrap().dummy.is_some() { + let preds = g.predecessors(&v).unwrap_or(vec![]); + preds.iter().for_each(|u| { + let u_node_ = g.node(u); + if let Some(u_node) = u_node_ { + let u_node_order = u_node.order.clone().unwrap_or(0) as i32; + if u_node.dummy.is_some() + && (&u_node_order < prev_north_border + || &u_node_order > next_north_border) + { + add_conflict(conflicts, u, &v); + } + } + }); + } + } + } + + fn visit_layer( + g: &mut Graph, + north: &Vec, + south: &Vec, + conflicts: &mut OrderedHashMap>, + ) { + let mut _prev_north_pos = -1; + let mut next_north_pos: i32 = -1; + let mut south_pos: usize = 0; + + let mut south_look_ahead = 0; + while south_look_ahead < south.len() { + let v = south[south_look_ahead].clone(); + if let Some(v_node) = g.node(&v) { + if v_node.dummy.is_some() && v_node.dummy.clone().unwrap() == "border" { + let predecessors_ = g.predecessors(&v); + if predecessors_.is_some() { + let predecessors = predecessors_.unwrap(); + if predecessors.len() > 0 { + next_north_pos = + g.node(&predecessors[0]).unwrap().order.clone().unwrap_or(0) as i32; + scan( + g, + &south, + &south_pos, + &south.len(), + &next_north_pos, + &(north.len() as i32), + conflicts, + ); + south_pos = south_look_ahead; + _prev_north_pos = next_north_pos.clone(); + } + } + } + + scan( + g, + south, + &south_pos, + &south.len(), + &next_north_pos, + &(north.len() as i32), + conflicts, + ); + } + + south_look_ahead += 1; + } + } + + layering.iter().reduce(|north, south| { + visit_layer(g, north, south, &mut conflicts); + + south + }); + + conflicts +} + +fn find_other_inner_segment_node( + g: &mut Graph, + v: &String, +) -> Option { + if g.node(v).unwrap().dummy.is_some() { + let preds = g.predecessors(v).unwrap_or(vec![]); + return preds + .iter() + .find(|u| g.node(u).unwrap().dummy.is_some()) + .cloned(); + } + + None +} + +pub fn add_conflict( + conflicts: &mut OrderedHashMap>, + v_: &String, + w_: &String, +) { + let mut v = v_.clone(); + let mut w = w_.clone(); + if v.cmp(&w) == Ordering::Greater { + let tmp = v; + v = w; + w = tmp; + } + + let _conflicts_v = conflicts.get(&v); + if _conflicts_v.is_none() { + conflicts.insert(v.clone(), OrderedHashMap::new()); + } + + let conflicts_v = conflicts.get_mut(&v).unwrap(); + conflicts_v.insert(w.clone(), true); +} + +pub fn has_conflict( + conflicts: &OrderedHashMap>, + v_: &String, + w_: &String, +) -> bool { + let mut v = v_; + let mut w = w_; + if v_.cmp(w_) == Ordering::Greater { + let tmp = v; + v = w; + w = tmp; + } + + let empty_hashmap: OrderedHashMap = OrderedHashMap::new(); + conflicts.get(v).unwrap_or(&empty_hashmap).contains_key(w) +} + +/* + * Try to align nodes into vertical "blocks" where possible. This algorithm + * attempts to align a node with one of its median neighbors. If the edge + * connecting a neighbor is a type-1 conflict then we ignore that possibility. + * If a previous node has already formed a block with a node after the node + * we're trying to form a block with, we also ignore that possibility - our + * blocks would be split in that scenario. + */ +// root -> 0, align -> 1 +pub fn vertical_alignment( + g: &Graph, + layering: &Vec>, + conflicts: &OrderedHashMap>, + neighbor_fn: Box, &String) -> Vec>, +) -> (OrderedHashMap, Vec) { + let mut root: OrderedHashMap = OrderedHashMap::new(); + let mut align: OrderedHashMap = OrderedHashMap::new(); + let mut pos: OrderedHashMap = OrderedHashMap::new(); + + // We cache the position here based on the layering because the graph and + // layering may be out of sync. The layering matrix is manipulated to + // generate different extreme alignments. + layering.iter().for_each(|layer| { + layer.iter().enumerate().for_each(|(order, v)| { + root.insert(v.clone(), v.clone()); + align.insert(v.clone(), v.clone()); + pos.insert(v.clone(), order); + }); + }); + + layering.iter().for_each(|layer| { + let mut prev_idx: i32 = -1; + layer.iter().for_each(|v| { + let mut ws: Vec = neighbor_fn(g, v); + if ws.len() > 0 { + ws.sort_by(|w1, w2| pos.get(w1).unwrap().cmp(pos.get(w2).unwrap())); + let mp = (ws.len() as f32 - 1.0) / 2.0; + let mut i = mp as usize; + let il = mp.ceil() as usize; + while i <= il { + let w = ws[i].clone(); + if align.get(v).unwrap() == v + && prev_idx < (pos.get(&w).cloned().unwrap() as i32) + && !has_conflict(conflicts, v, &w) + { + align.insert(w.clone(), v.clone()); + + root.insert(v.clone(), root.get(&w).unwrap().clone()); + align.insert(v.clone(), root.get(&w).unwrap().clone()); + + prev_idx = pos.get(&w).unwrap().clone() as i32; + } + + i += 1; + } + } + }); + }); + + return (root, align.into_values()); +} + +pub fn horizontal_compaction( + g: &Graph, + layering: &Vec>, + root: &OrderedHashMap, + align: &Vec, + reverse_sep: bool, +) -> OrderedHashMap { + // This portion of the algorithm differs from BK due to a number of problems. + // Instead of their algorithm we construct a new block graph and do two + // sweeps. The first sweep places blocks with the smallest possible + // coordinates. The second sweep removes unused space by moving blocks to the + // greatest coordinates without violating separation. + let mut xs: OrderedHashMap = OrderedHashMap::new(); + let block_g: Graph = + build_block_graph(g, layering, root, reverse_sep); + let border_type = if reverse_sep { + BorderTypeName::BorderLeft + } else { + BorderTypeName::BorderRight + }; + + fn iterate( + set_xs_func: fn( + &String, + &mut OrderedHashMap, + &Graph, + &Graph, + &BorderTypeName, + ), + next_nodes_func: Box, &String) -> Vec>, + block_g: &Graph, + xs: &mut OrderedHashMap, + g: &Graph, + border_type: &BorderTypeName, + ) { + let mut stack = block_g.nodes(); + let mut elem = stack.pop(); + let mut visited: OrderedHashMap = OrderedHashMap::new(); + while elem.is_some() { + let elem_ = elem.unwrap(); + if visited.contains_key(&elem_) { + set_xs_func(&elem_, xs, block_g, g, border_type); + } else { + visited.insert(elem_.clone(), true); + stack.push(elem_.clone()); + stack.append(&mut (next_nodes_func(block_g, &elem_) as Vec)); + } + + elem = stack.pop(); + } + } + + // First pass, assign smallest coordinates + fn pass1( + elem: &String, + xs: &mut OrderedHashMap, + block_g: &Graph, + _g: &Graph, + _border_type: &BorderTypeName, + ) { + let in_edges = block_g.in_edges(elem, None).unwrap_or(vec![]); + let val: f32 = in_edges.iter().fold(0.0, |acc, e| { + let ev: f32 = xs.get(&e.v).cloned().unwrap() + + (block_g.edge_with_obj(&e).cloned().unwrap_or(0.0)); + acc.max(ev) + }); + xs.insert(elem.clone(), val); + } + + // Second pass, assign greatest coordinates + fn pass2( + elem: &String, + xs: &mut OrderedHashMap, + block_g: &Graph, + g: &Graph, + border_type: &BorderTypeName, + ) { + let out_edges = block_g.out_edges(elem, None).unwrap_or(vec![]); + let min: f64 = out_edges.iter().fold(f64::INFINITY, |acc, e| { + let ev: f32 = xs.get(&e.w).cloned().unwrap() + - (block_g.edge_with_obj(&e).cloned().unwrap_or(0.0)); + + acc.min(ev as f64) + }); + + let node = g.node(elem).unwrap(); + if min != f64::INFINITY + && node.border_type.is_some() + && node.border_type.as_ref().unwrap() != border_type + { + xs.insert(elem.clone(), xs.get(elem).cloned().unwrap().max(min as f32)); + } + } + + iterate( + pass1, + Box::new(|block_g, v| -> Vec { block_g.predecessors(v).unwrap_or(vec![]) }), + &block_g, + &mut xs, + g, + &border_type, + ); + + iterate( + pass2, + Box::new(|block_g, v| -> Vec { block_g.predecessors(v).unwrap_or(vec![]) }), + &block_g, + &mut xs, + g, + &border_type, + ); + + // Assign x coordinates to all nodes + align.iter().for_each(|v| { + xs.insert(v.clone(), xs.get(root.get(&v).unwrap()).cloned().unwrap()); + }); + + xs +} + +pub fn build_block_graph( + g: &Graph, + layering: &Vec>, + root: &OrderedHashMap, + reverse_sep: bool, +) -> Graph { + let mut block_graph: Graph = Graph::new(None); + let graph_label = g.graph(); + let sep_fn: Box, &String, &String) -> f32> = + sep( + graph_label.nodesep.as_ref().unwrap(), + graph_label.edgesep.as_ref().unwrap(), + &reverse_sep, + ); + + layering.iter().for_each(|layer| { + let mut u: Option = None; + layer.iter().for_each(|v| { + let v_root = root.get(v).unwrap(); + block_graph.set_node(v_root.clone(), None); + if u.is_some() { + let u_ = u.as_ref().unwrap(); + let u_root = root.get(u_).unwrap(); + let prev_max = block_graph + .edge(&u_root, &v_root, None) + .cloned() + .unwrap_or(0.0); + + let _ = block_graph.set_edge( + &u_root, + &v_root, + Some((sep_fn(g, v, u_) as f32).max(prev_max)), + None, + ); + } + u = Some(v.clone()); + }); + }); + + block_graph +} + +/* + * Returns the alignment that has the smallest width of the given alignments. + */ +pub fn find_smallest_width_alignment<'a>( + g: &Graph, + xss: &'a OrderedHashMap>, +) -> &'a OrderedHashMap { + xss.values() + .min_by(|xs1, xs2| { + let mut max1 = f64::NEG_INFINITY; + let mut min1 = f64::INFINITY; + + xs1.iter().for_each(|(v, x)| { + let half_width = width(g, v) / 2.0; + max1 = max1.max((*x + half_width) as f64); + min1 = min1.min((*x - half_width) as f64); + }); + + let r1 = max1 - min1; + + let mut max2 = f64::NEG_INFINITY; + let mut min2 = f64::INFINITY; + + xs2.iter().for_each(|(v, x)| { + let half_width = width(g, v) / 2.0; + max2 = max2.max((*x + half_width) as f64); + min2 = min2.min((*x - half_width) as f64); + }); + + let r2 = max2 - min2; + + r1.total_cmp(&r2) + }) + .unwrap() +} + +/* + * Align the coordinates of each of the layout alignments such that + * left-biased alignments have their minimum coordinate at the same point as + * the minimum coordinate of the smallest width alignment and right-biased + * alignments have their maximum coordinate at the same point as the maximum + * coordinate of the smallest width alignment. + */ +fn align_coordinates( + xss: &mut OrderedHashMap>, + align_to: &OrderedHashMap, +) { + let align_to_vals: Vec = align_to.values().cloned().collect(); + let align_to_min = align_to_vals + .iter() + .min_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap() + .clone(); + let align_to_max = align_to_vals + .iter() + .max_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap() + .clone(); + + vec!["u", "d"].iter().for_each(|vert| { + vec!["l", "r"].iter().for_each(|horiz| { + let alignment = vert.to_string() + horiz; + let xs = xss.get(&alignment).unwrap(); + if xs == align_to { + return; + } + + let xs_vals: Vec = xs.values().cloned().collect(); + let delta = if *horiz == "l" { + align_to_min + - *xs_vals + .iter() + .min_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap() + } else { + align_to_max + - *xs_vals + .iter() + .max_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap() + }; + + if delta != 0.0 { + let _xs = xss.get_mut(&alignment).unwrap(); + _xs.values_mut().for_each(|x| { + *x += delta; + }); + } + }) + }) +} + +pub fn balance( + xss: &OrderedHashMap>, + align: Option, +) -> OrderedHashMap { + let mut xss_clone = xss.clone(); + if let Some(ul) = xss_clone.get_mut(&"ul".to_string()) { + let keys: Vec = ul.keys().cloned().collect(); + keys.iter().for_each(|v| { + if align.is_some() { + let empty_hash: OrderedHashMap = OrderedHashMap::new(); + let empty_string = "".to_string(); + let _balance = xss + .get(align.as_ref().unwrap_or(&empty_string)) + .unwrap_or(&empty_hash) + .get(v) + .cloned() + .unwrap_or(0.0); + let item = ul.get_mut(v).unwrap(); + *item = _balance; + } else { + let mut xs: Vec = xss + .values() + .map(|_xs| _xs.get(v).cloned().unwrap_or(f64::INFINITY as f32)) + .collect(); + xs.sort_by(|f1, f2| f1.total_cmp(f2)); + let xs1 = xs.get(1).cloned().unwrap_or(0.0); + let xs2 = xs.get(2).cloned().unwrap_or(0.0); + let item = ul.get_mut(v).unwrap(); + *item = (xs1 + xs2) / 2.0; + } + }); + } + + xss_clone.get_mut(&"ul".to_string()).unwrap().to_owned() +} + +pub fn position_x(g: &mut Graph) -> OrderedHashMap { + let layering = util::build_layer_matrix(g); + let mut conflicts = find_type_1_conflicts(g, &layering); + conflicts.extend(find_type_2_conflicts(g, &layering)); + + let mut xss: OrderedHashMap> = OrderedHashMap::new(); + let mut adjusted_layering: Option>> = None; + vec!["u", "d"].iter().for_each(|vert| { + adjusted_layering = Some(if vert == &"u" { + layering.clone() + } else { + let mut layering_ = layering.clone(); + layering_.reverse(); + layering_ + }); + + vec!["l", "r"].iter().for_each(|horiz| { + if horiz == &"r" { + adjusted_layering + .as_mut() + .unwrap() + .iter_mut() + .for_each(|inner| inner.reverse()); + } + + let neighbor_fn: Box< + dyn Fn(&Graph, &String) -> Vec, + > = if vert == &"u" { + Box::new( + |g: &Graph, v: &String| -> Vec { + g.predecessors(v).unwrap_or(vec![]) + }, + ) + } else { + Box::new( + |g: &Graph, v: &String| -> Vec { + g.successors(v).unwrap_or(vec![]) + }, + ) + }; + let align = vertical_alignment( + g, + adjusted_layering.as_ref().unwrap(), + &conflicts, + neighbor_fn, + ); + let mut xs = horizontal_compaction( + g, + adjusted_layering.as_ref().unwrap(), + &align.0, + &align.1, + horiz == &"r", + ); + if horiz == &"r" { + let mut xs_: OrderedHashMap = OrderedHashMap::new(); + xs.iter().for_each(|(k, v)| { + xs_.insert(k.clone(), -v.clone()); + }); + xs = xs_; + } + + xss.insert(String::from(vert.to_string() + horiz), xs); + }); + }); + + let smallest_width = find_smallest_width_alignment(g, &xss).clone(); + align_coordinates(&mut xss, &smallest_width); + return balance(&xss, g.graph().align.clone()); +} + +fn sep( + node_sep: &f32, + edge_sep: &f32, + reverse_sep: &bool, +) -> Box, &String, &String) -> f32> { + let node_sep_ = node_sep.clone(); + let edge_sep_ = edge_sep.clone(); + let reverse_sep_ = reverse_sep.clone(); + #[allow(unused_assignments)] + Box::new( + move |g: &Graph, v: &String, w: &String| -> f32 { + let v_label = g.node(v).unwrap(); + let w_label = g.node(w).unwrap(); + let mut sum: f32 = 0.0; + let mut delta: f32 = 0.0; + + sum += v_label.width / 2.0; + if let Some(v_label_labelpos) = v_label.labelpos.as_ref() { + if v_label_labelpos == "l" { + delta = -v_label.width / 2.0; + } else if v_label_labelpos == "r" { + delta = v_label.width / 2.0; + } + } + if delta != 0.0 { + sum += if reverse_sep_ { delta } else { -delta } + } + delta = 0.0; + + sum += if v_label.dummy.is_some() { + edge_sep_ + } else { + node_sep_ + } / 2.0; + sum += if w_label.dummy.is_some() { + edge_sep_ + } else { + node_sep_ + } / 2.0; + + sum += w_label.width / 2.0; + if let Some(w_label_labelpos) = w_label.labelpos.as_ref() { + if w_label_labelpos == "l" { + delta = w_label.width / 2.0; + } else if w_label_labelpos == "r" { + delta = -w_label.width / 2.0; + } + } + if delta != 0.0 { + sum += if reverse_sep_ { delta } else { -delta } + } + delta = 0.0; + + sum + }, + ) +} + +fn width(g: &Graph, v: &String) -> f32 { + g.node(v).unwrap().width +} diff --git a/patches/dagre_rust/src/layout/position/mod.rs b/patches/dagre_rust/src/layout/position/mod.rs new file mode 100644 index 0000000..f2ef0c9 --- /dev/null +++ b/patches/dagre_rust/src/layout/position/mod.rs @@ -0,0 +1,36 @@ +pub mod bk; + +use crate::layout::position::bk::position_x; +use crate::layout::util; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; + +pub fn position(g: &mut Graph) { + let mut ncg: Graph = util::as_non_compound_graph(g); + + position_y(&mut ncg); + position_x(&mut ncg).iter().for_each(|(v, x)| { + g.node_mut(v).unwrap().x = x.clone(); + g.node_mut(v).unwrap().y = ncg.node(v).unwrap().y; + }); +} + +fn position_y(g: &mut Graph) { + let layering = util::build_layer_matrix(g); + let rank_sep = g.graph().ranksep.clone().unwrap(); + let mut prev_y = 0.0; + layering.iter().for_each(|layer| { + let max_height: f32 = layer + .iter() + .map(|v| g.node(v).unwrap().height as i32) + .max() + .unwrap_or(0) as f32; + + layer.iter().for_each(|v| { + let node = g.node_mut(v).unwrap(); + node.y = prev_y + max_height / 2.0; + }); + + prev_y += max_height + rank_sep; + }); +} diff --git a/patches/dagre_rust/src/layout/rank/feasible_tree.rs b/patches/dagre_rust/src/layout/rank/feasible_tree.rs new file mode 100644 index 0000000..7726948 --- /dev/null +++ b/patches/dagre_rust/src/layout/rank/feasible_tree.rs @@ -0,0 +1,142 @@ +use crate::layout::rank::util::slack; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::{Edge, Graph, GraphOption}; + +/* + * Constructs a spanning tree with tight edges and adjusted the input node's + * ranks to achieve this. A tight edge is one that is has a length that matches + * its "minlen" attribute. + * + * The basic structure for this function is derived from Gansner, et al., "A + * Technique for Drawing Directed Graphs." + * + * Pre-conditions: + * + * 1. Graph must be a DAG. + * 2. Graph must be connected. + * 3. Graph must have at least one node. + * 5. Graph nodes must have been previously assigned a "rank" property that + * respects the "minlen" property of incident edges. + * 6. Graph edges must have a "minlen" property. + * + * Post-conditions: + * + * - Graph nodes will have their rank adjusted to ensure that all edges are + * tight. + * + * Returns a tree (undirected graph) that is constructed using only "tight" + * edges. + */ + +pub fn feasible_tree( + g: &mut Graph, +) -> Graph { + let mut t: Graph = Graph::new(Some(GraphOption { + directed: Some(false), + multigraph: Some(false), + compound: Some(false), + })); + + // Choose arbitrary node from which to start our tree + let start = g.nodes().first().cloned().unwrap_or("".to_string()); + let size = g.node_count(); + t.set_node(start, Some(GraphNode::default())); + + let mut edge: Option; + let mut delta: Option; + while tight_tree(&mut t, g) < size { + edge = find_min_stack_edge(&t, g); + if let Some(edge_) = edge { + if t.has_node(&edge_.v) { + delta = Some(slack(g, &edge_)); + } else { + delta = Some(-1 * slack(g, &edge_)); + } + shift_ranks(&t, g, delta.unwrap_or(0)); + } + } + + t +} + +/* + * Finds a maximal tree of tight edges and returns the number of nodes in the + * tree. + */ +fn tight_tree( + t: &mut Graph, + g: &Graph, +) -> usize { + fn dfs( + v: &String, + t: &mut Graph, + g: &Graph, + ) { + let node_edges = g.node_edges(v, None).unwrap_or(vec![]); + for node_edge in node_edges { + let edge_v = node_edge.v.clone(); + let mut _w: Option<&String> = None; + if v == &edge_v { + _w = Some(&node_edge.w); + } else { + _w = Some(&edge_v); + } + let w = _w.unwrap().clone(); + if !t.has_node(&w) && slack(g, &node_edge) == 0 { + t.set_node(w.clone(), Some(GraphNode::default())); + let _ = t.set_edge(&v, &w, Some(GraphEdge::default()), None); + dfs(&w, t, g); + } + } + } + + let nodes = t.nodes(); + for node_id in nodes.into_iter() { + dfs(&node_id, t, g); + } + return t.node_count(); +} + +/* + * Finds the edge with the smallest slack that is incident on tree and returns + * it. + */ +fn find_min_stack_edge( + t: &Graph, + g: &Graph, +) -> Option { + let edges = g.edges(); + let result = edges + .iter() + .map(|e| { + let mut e_: Option = None; + if t.has_node(&e.v) != t.has_node(&e.w) { + e_ = Some(slack(g, e)); + } + (e, e_) + }) + .filter(|(_, e_)| e_.is_some()) + .min_by(|(_, e1_), (_, e2_)| { + return e1_.unwrap().cmp(&e2_.unwrap()); + }); + + if result.is_some() { + Some(result.unwrap().0.clone()) + } else { + None + } +} + +fn shift_ranks( + t: &Graph, + g: &mut Graph, + delta: i32, +) { + let nodes = t.nodes(); + for node_id in nodes.into_iter() { + let node_ = g.node_mut(&node_id); + if let Some(node) = node_ { + node.rank = Some(node.rank.unwrap_or(0) + delta); + } + } +} diff --git a/patches/dagre_rust/src/layout/rank/mod.rs b/patches/dagre_rust/src/layout/rank/mod.rs new file mode 100644 index 0000000..341782b --- /dev/null +++ b/patches/dagre_rust/src/layout/rank/mod.rs @@ -0,0 +1,53 @@ +pub mod feasible_tree; +pub mod network_simplex; +pub mod util; + +use crate::layout::rank::feasible_tree::feasible_tree; +use crate::layout::rank::network_simplex::network_simplex; +use crate::layout::rank::util::longest_path; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::Graph; + +/* + * Assigns a rank to each node in the input graph that respects the "minlen" + * constraint specified on edges between nodes. + * + * This basic structure is derived from Gansner, et al., "A Technique for + * Drawing Directed Graphs." + * + * Pre-conditions: + * + * 1. Graph must be a connected DAG + * 2. Graph nodes must be objects + * 3. Graph edges must have "weight" and "minlen" attributes + * + * Post-conditions: + * + * 1. Graph nodes will have a "rank" attribute based on the results of the + * algorithm. Ranks can start at any index (including negative), we'll + * fix them up later. + */ + +pub fn rank(g: &mut Graph) { + let _ranker = g.graph().ranker.clone(); + match _ranker { + Some(ranker) => { + let ranker_str = &*ranker; + if ranker_str == "network-simplex" { + network_simplex(g); + } else if ranker_str == "tight-tree" { + tight_tree_ranker(g); + } else if ranker_str == "longest-path" { + longest_path(g); + } + } + _ => { + network_simplex(g); + } + } +} + +fn tight_tree_ranker(g: &mut Graph) { + longest_path(g); + feasible_tree(g); +} diff --git a/patches/dagre_rust/src/layout/rank/network_simplex.rs b/patches/dagre_rust/src/layout/rank/network_simplex.rs new file mode 100644 index 0000000..4666e99 --- /dev/null +++ b/patches/dagre_rust/src/layout/rank/network_simplex.rs @@ -0,0 +1,343 @@ +use crate::layout::rank::feasible_tree::feasible_tree; +use crate::layout::rank::util::{longest_path, slack}; +use crate::layout::util::simplify_ref; +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::algo::postorder::postorder; +use graphlib_rust::algo::preorder::preorder; +use graphlib_rust::{Edge, Graph}; +use ordered_hashmap::OrderedHashMap; + +/* + * The network simplex algorithm assigns ranks to each node in the input graph + * and iteratively improves the ranking to reduce the length of edges. + * + * Preconditions: + * + * 1. The input graph must be a DAG. + * 2. All nodes in the graph must have an object value. + * 3. All edges in the graph must have "minlen" and "weight" attributes. + * + * Postconditions: + * + * 1. All nodes in the graph will have an assigned "rank" attribute that has + * been optimized by the network simplex algorithm. Ranks start at 0. + * + * + * A rough sketch of the algorithm is as follows: + * + * 1. Assign initial ranks to each node. We use the longest path algorithm, + * which assigns ranks to the lowest position possible. In general this + * leads to very wide bottom ranks and unnecessarily long edges. + * 2. Construct a feasible tight tree. A tight tree is one such that all + * edges in the tree have no slack (difference between length of edge + * and minlen for the edge). This by itself greatly improves the assigned + * rankings by shorting edges. + * 3. Iteratively find edges that have negative cut values. Generally a + * negative cut value indicates that the edge could be removed and a new + * tree edge could be added to produce a more compact graph. + * + * Much of the algorithms here are derived from Gansner, et al., "A Technique + * for Drawing Directed Graphs." The structure of the file roughly follows the + * structure of the overall algorithm. + */ + +pub fn network_simplex(g: &mut Graph) { + simplify_ref(g); // =g = simplify(g); + longest_path(g); // =init_rank + + let mut t: Graph = feasible_tree(g); + init_low_lim_values(&mut t, None); + init_cut_values(&mut t, g); + + let mut e; + let mut f; + while { + e = leave_edge(&t); + e.is_some() + } { + f = enter_edge(&t, &g, &e.clone().unwrap()); + if f.is_some() { + exchange_edges(&mut t, g, &e.clone().unwrap(), f.unwrap()); + } + } +} + +/* + * Initializes cut values for all edges in the tree. + */ +fn init_cut_values( + t: &mut Graph, + g: &mut Graph, +) { + let node_ids = g.nodes(); + let mut vs = postorder(g, &node_ids); + vs.pop(); // removing last value // vs = vs.slice(0, vs.length - 1); + for node_id in vs { + assign_cut_value(t, g, &node_id); + } +} + +/* + * Given the tight tree, its graph, and a child in the graph calculate and + * return the cut value for the edge between the child and its parent. + */ +fn assign_cut_value( + t: &mut Graph, + g: &mut Graph, + child: &String, +) { + let cutvalue = calc_cut_value(t, g, child); + let child_lab_ = t.node_mut(child); + if let Some(child_lab) = child_lab_ { + let parent = child_lab.parent.clone().unwrap_or("".to_string()); + let edge_label_ = t.edge_mut(&child, &parent, None); + if let Some(edge_label) = edge_label_ { + edge_label.cutvalue = Some(cutvalue); + } + } +} + +/* + * Given the tight tree, its graph, and a child in the graph calculate and + * return the cut value for the edge between the child and its parent. + */ +fn calc_cut_value( + t: &mut Graph, + g: &mut Graph, + child: &String, +) -> f32 { + // The accumulated cut value for the edge between this node and its parent + let mut cut_value = 0.0; + let child_lab_ = t.node_mut(child); + if let Some(child_lab) = child_lab_ { + let parent = child_lab.parent.clone().unwrap_or("".to_string()); + // True if the child is on the tail end of the edge in the directed graph + let mut child_is_tail = true; + // The graph's view of the tree edge we're inspecting + let mut graph_edge = g.edge_mut(child, &parent, None); + + if graph_edge.is_none() { + child_is_tail = false; + graph_edge = g.edge_mut(&parent, &child, None); + } + + cut_value = graph_edge + .cloned() + .unwrap_or(GraphEdge::default()) + .weight + .unwrap_or(0.0); + let edge_objs_ = g.node_edges(child, None); + if let Some(edge_objs) = edge_objs_ { + for e in edge_objs { + let is_out_edge = &e.v == child; + let other = if is_out_edge { + e.w.clone() + } else { + e.v.clone() + }; + + if other != parent { + let points_to_head = is_out_edge == child_is_tail; + let other_weight = g + .edge_with_obj(&e) + .unwrap_or(&GraphEdge::default()) + .weight + .unwrap_or(0.0); + + cut_value += if points_to_head { + other_weight + } else { + -other_weight + }; + + if is_tree_edge(t, child, &other) { + let out_cut_value = t + .edge(&child, &other, None) + .unwrap_or(&GraphEdge::default()) + .cutvalue + .unwrap_or(0.0); + cut_value += if points_to_head { + -out_cut_value + } else { + out_cut_value + } + } + } + } + } + } + + cut_value +} + +fn init_low_lim_values(tree: &mut Graph, root_: Option) { + let mut root = tree.nodes().first().cloned().unwrap_or("".to_string()); + if root_.is_some() { + root = root_.unwrap(); + } + let mut visited: OrderedHashMap = OrderedHashMap::new(); + dfs_assign_low_lim(tree, &mut visited, 1, &root, None); +} + +fn dfs_assign_low_lim( + tree: &mut Graph, + visited: &mut OrderedHashMap, + next_lim_: usize, + v: &String, + parent: Option<&String>, +) -> usize { + let low = next_lim_.clone(); + let mut next_lim = next_lim_.clone(); + + visited.entry(v.clone()).or_insert(true); + let neighbors_ = tree.neighbors(v); + if let Some(neighbors) = neighbors_ { + for w in neighbors.into_iter() { + if !visited.contains_key(&w) { + next_lim = dfs_assign_low_lim(tree, visited, next_lim.clone(), &w, Some(v)); + } + } + } + + let label_ = tree.node_mut(v); + if let Some(label) = label_ { + label.low = Some(low); + label.lim = Some(next_lim.clone()); + next_lim += 1; + + if parent.is_some() { + label.parent = Some(parent.cloned().unwrap()); + } else { + // TODO should be able to remove this when we incrementally update low lim + label.parent = None; + } + } + + next_lim +} + +fn leave_edge(tree: &Graph) -> Option { + let edge_objs = tree.edges(); + edge_objs + .iter() + .find(|edge_obj| { + tree.edge_with_obj(edge_obj) + .unwrap_or(&GraphEdge::default()) + .cutvalue + .unwrap_or(0.0) + < 0.0 + }) + .cloned() +} + +fn enter_edge( + t: &Graph, + g: &Graph, + edge: &Edge, +) -> Option { + let mut v = edge.v.clone(); + let mut w = edge.w.clone(); + + // For the rest of this function we assume that v is the tail and w is the + // head, so if we don't have this edge in the graph we should flip it to + // match the correct orientation. + if !g.has_edge(&v, &w, None) { + v = edge.w.clone(); + w = edge.v.clone(); + } + + let v_label = t.node(&v).cloned().unwrap_or(GraphNode::default()); + let w_label = t.node(&w).cloned().unwrap_or(GraphNode::default()); + let mut tail_label = &v_label; + let mut flip = false; + + // If the root is in the tail of the edge then we need to flip the logic that + // checks for the head and tail nodes in the candidates function below. + if v_label.lim.clone().unwrap_or(0) > w_label.lim.clone().unwrap_or(0) { + tail_label = &w_label; + flip = true; + } + + let edge_objs = g.edges(); + let candidates = edge_objs.iter().filter(|edge_obj| { + let v_node = t.node(&edge_obj.v).cloned().unwrap_or(GraphNode::default()); + let w_node = t.node(&edge_obj.w).cloned().unwrap_or(GraphNode::default()); + flip == is_descendant(&v_node, tail_label) && flip != is_descendant(&w_node, tail_label) + }); + + candidates + .min_by(|e1, e2| slack(g, e1).cmp(&slack(g, e2))) + .cloned() +} + +fn exchange_edges( + t: &mut Graph, + g: &mut Graph, + e: &Edge, + f: Edge, +) { + let v = e.v.clone(); + let w = e.w.clone(); + t.remove_edge(&v, &w, None); + let _ = t.set_edge(&f.v, &f.w, Some(GraphEdge::default()), None); + init_low_lim_values(t, None); + init_cut_values(t, g); + update_ranks(t, g); +} + +fn update_ranks( + t: &mut Graph, + g: &mut Graph, +) { + let root = t + .nodes() + .into_iter() + .find(|v| !g.node(v).unwrap_or(&GraphNode::default()).parent.is_none()) + .unwrap_or("".to_string()); + let mut vs = preorder(t, &vec![root]); + vs = vs.iter().skip(1).cloned().collect(); // vs = vs.slice(1); + for v in vs.into_iter() { + let parent = t.node(&v).unwrap_or(&GraphNode::default()).parent.clone(); + let _parent = parent.clone().unwrap_or("".to_string()); + let mut edge = g.edge(&v, &_parent, None); + let mut flipped = false; + if edge.is_none() { + edge = g.edge(&_parent, &v, None); + flipped = true; + } + + let minlen = if flipped { + edge.unwrap_or(&GraphEdge::default()).minlen.unwrap_or(0.0) + } else { + -edge.unwrap_or(&GraphEdge::default()).minlen.unwrap_or(0.0) + }; + + let parent_rank = g + .node(&_parent) + .unwrap_or(&GraphNode::default()) + .rank + .unwrap_or(0); + let v_node_ = g.node_mut(&v); + if let Some(v_node) = v_node_ { + v_node.rank = Some(parent_rank + (minlen as i32)); + } + } +} + +/* + * Returns true if the edge is in the tree. + */ +fn is_tree_edge(tree: &Graph, u: &String, v: &String) -> bool { + tree.has_edge(&u, &v, None) +} + +/* + * Returns true if the specified node is descendant of the root node per the + * assigned low and lim attributes in the tree. + */ +fn is_descendant(v_label: &GraphNode, root_label: &GraphNode) -> bool { + let low = root_label.low.clone().unwrap_or(0); + let v_lim = v_label.lim.clone().unwrap_or(0); + let root_lim = root_label.lim.clone().unwrap_or(0); + low <= v_lim && v_lim <= root_lim +} diff --git a/patches/dagre_rust/src/layout/rank/util.rs b/patches/dagre_rust/src/layout/rank/util.rs new file mode 100644 index 0000000..a6f382c --- /dev/null +++ b/patches/dagre_rust/src/layout/rank/util.rs @@ -0,0 +1,99 @@ +use crate::{GraphConfig, GraphEdge, GraphNode}; +use graphlib_rust::{Edge, Graph}; +use ordered_hashmap::OrderedHashMap; + +/* + * Initializes ranks for the input graph using the longest path algorithm. This + * algorithm scales well and is fast in practice, it yields rather poor + * solutions. Nodes are pushed to the lowest layer possible, leaving the bottom + * ranks wide and leaving edges longer than necessary. However, due to its + * speed, this algorithm is good for getting an initial ranking that can be fed + * into other algorithms. + * + * This algorithm does not normalize layers because it will be used by other + * algorithms in most cases. If using this algorithm directly, be sure to + * run normalize at the end. + * + * Pre-conditions: + * + * 1. Input graph is a DAG. + * 2. Input graph node labels can be assigned properties. + * + * Post-conditions: + * + * 1. Each node will be assign an (unnormalized) "rank" property. + */ + +pub fn longest_path(g: &mut Graph) { + let mut visited: OrderedHashMap = OrderedHashMap::new(); + + fn dfs( + v: &String, + g: &mut Graph, + visited: &mut OrderedHashMap, + ) -> i32 { + let node_label = g.node(v); + if visited.contains_key(v) { + return node_label + .cloned() + .unwrap_or(GraphNode::default()) + .rank + .unwrap_or(0); + } + visited.insert(v.clone(), true); + + let ranks: Vec = g + .out_edges(v, None) + .unwrap_or(vec![]) + .iter() + .map(|e| { + dfs(&e.w, g, visited) + - (g.edge_with_obj(&e) + .cloned() + .unwrap_or(GraphEdge::default()) + .minlen + .unwrap_or(0.0) + .round() as i32) + }) + .collect(); + let rank: i32 = ranks.iter().min().cloned().unwrap_or(0) as i32; + { + let _node_label = g.node_mut(v); + if let Some(node_label) = _node_label { + node_label.rank = Some(rank.clone()); + } + } + return rank; + } + + for node_id in g.sources().into_iter() { + dfs(&node_id, g, &mut visited); + } +} + +/* + * Returns the amount of slack for the given edge. The slack is defined as the + * difference between the length of the edge and its minimum length. + */ +pub fn slack(g: &Graph, e: &Edge) -> i32 { + let w_rank = g + .node(&e.w) + .cloned() + .unwrap_or(GraphNode::default()) + .rank + .unwrap_or(0); + let v_rank = g + .node(&e.v) + .cloned() + .unwrap_or(GraphNode::default()) + .rank + .unwrap_or(0); + let minlen = g + .edge_with_obj(e) + .cloned() + .unwrap_or(GraphEdge::default()) + .minlen + .unwrap_or(10.0) + .round() as i32; + return w_rank - v_rank - minlen; +} diff --git a/patches/dagre_rust/src/layout/util.rs b/patches/dagre_rust/src/layout/util.rs new file mode 100644 index 0000000..18e76b6 --- /dev/null +++ b/patches/dagre_rust/src/layout/util.rs @@ -0,0 +1,346 @@ +use crate::layout::{GraphConfig, GraphEdge, GraphNode}; +use crate::GraphEdgePoint; +use graphlib_rust::{Graph, GraphOption}; +use ordered_hashmap::OrderedHashMap; + +static mut UNIQUE_STARTER: usize = 0; + +pub fn unique_id() -> usize { + unsafe { + UNIQUE_STARTER += 1; + return UNIQUE_STARTER; + } +} + +/* + * Adds a dummy node to the graph and return v. + */ +pub fn add_dummy_node( + graph: &mut Graph, + node_type: String, + data: GraphNode, + name: String, +) -> String { + // Generating Random Id + let mut node_id = format!("{}{}", name, unique_id()); + while graph.has_node(&node_id) { + node_id = format!("{}{}", name, unique_id()); + } + + // Setting in Graph + let mut node_data = data.clone(); + node_data.dummy = Some(node_type); + graph.set_node(node_id.clone(), Some(node_data)); + return node_id; +} + +/* + * Returns a new graph with only simple edges. Handles aggregation of data + * associated with multi-edges. + */ +pub fn simplify( + g: &Graph, +) -> Graph { + let mut simplified: Graph = Graph::new(Some(GraphOption { + directed: Some(true), + multigraph: None, + compound: None, + })); + + let nodes = g.nodes(); + let edges = g.edges(); + for node_id in nodes.into_iter() { + simplified.set_node(node_id.clone(), g.node(&node_id).cloned()); + } + for edge_obj in edges.into_iter() { + let edge_label_ = g.edge_with_obj(&edge_obj); + let mut simple_label = simplified + .edge(&edge_obj.v, &edge_obj.w, None) + .cloned() + .unwrap_or(GraphEdge::default()); + + if let Some(edge_label) = edge_label_ { + if let Some(minlen) = edge_label.minlen { + simple_label.minlen = Some(std::cmp::max( + simple_label.minlen.unwrap_or(1.0) as i32, + minlen as i32, + ) as f32); + } + if let Some(weight) = edge_label.weight { + simple_label.weight = Some(simple_label.weight.unwrap_or(0.0) + weight); + } + } + + let _ = simplified.set_edge(&edge_obj.v, &edge_obj.w, Some(simple_label), None); + } + + simplified +} + +/* + * it implement same logic as simplify do but, it uses Ref instead of creating new graph + */ +pub fn simplify_ref(g: &mut Graph) { + let edges = g.edges(); + for edge_obj in edges.into_iter() { + let edge_label_ = g.edge_mut_with_obj(&edge_obj); + if let Some(edge_label) = edge_label_ { + if edge_label.weight.is_none() { + edge_label.weight = Some(0.0); + } + if edge_label.minlen.is_none() { + edge_label.minlen = Some(1.0); + } + } + } +} + +pub fn as_non_compound_graph( + g: &mut Graph, +) -> Graph { + let mut simplified: Graph = Graph::new(Some(GraphOption { + directed: Some(true), + multigraph: Some(true), + compound: Some(false), + })); + simplified.set_graph(g.graph().clone()); + + let nodes = g.nodes(); + for v in nodes.into_iter() { + if g.children(&v).len() == 0 { + simplified.set_node( + v.clone(), + Some(g.node(&v).cloned().unwrap_or(GraphNode::default())), + ); + } + } + let edge_objs = g.edges(); + for e in edge_objs.into_iter() { + let _ = simplified.set_edge_with_obj(&e, g.edge_with_obj(&e).cloned()); + } + + return simplified; +} + +pub fn transfer_node_edge_labels( + source: &Graph, + destination: &mut Graph, +) { + let nodes = source.nodes(); + for v in nodes.into_iter() { + if source.children(&v).len() == 0 { + destination.set_node( + v.clone(), + Some(source.node(&v).cloned().unwrap_or(GraphNode::default())), + ); + } + } + + let edge_objs = source.edges(); + for e in edge_objs.into_iter() { + let _ = destination.set_edge_with_obj(&e, source.edge_with_obj(&e).cloned()); + } +} + +pub struct Rect { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, +} + +/* + * Finds where a line starting at point ({x, y}) would intersect a rectangle + * ({x, y, width, height}) if it were pointing at the rectangle's center. + */ +pub fn intersect_rect(rect: &Rect, point: &GraphEdgePoint) -> GraphEdgePoint { + let x = rect.x; + let y = rect.y; + + // Rectangle intersection algorithm from: + // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes + let dx = point.x - x; + let dy = point.y - y; + let w = rect.width / 2.0; + let h = rect.height / 2.0; + + if dx == 0.0 && dy == 0.0 { + panic!("Not possible to find intersection inside of the rectangle"); + } + + let (sx, sy) = if (dy.abs() * w) > (dx.abs() * h) { + // Intersection is top or bottom of rect. + if dy < 0.0 { + (-h * dx / dy, -h) + } else { + (h * dx / dy, h) + } + } else { + // Intersection is left or right of rect. + if dx < 0.0 { + (-w, -w * dy / dx) + } else { + (w, w * dy / dx) + } + }; + + GraphEdgePoint { + x: x + sx, + y: y + sy, + } +} + +/* + * Given a DAG with each node assigned "rank" and "order" properties, this + * function will produce a matrix with the ids of each node. + */ +pub fn build_layer_matrix(g: &Graph) -> Vec> { + let mut layering: Vec> = + (0..=max_rank(g)).map(|_| OrderedHashMap::new()).collect(); + + g.nodes().iter().for_each(|v| { + let node = g.node(v).unwrap(); + let rank = node.rank.unwrap_or(0) as usize; + let layer: &mut OrderedHashMap = layering.get_mut(rank).unwrap(); + layer.insert(node.order.unwrap_or(0), v.clone()); + }); + + return layering + .into_iter() + .map(|layer| -> Vec { + let mut keys: Vec = layer.keys().cloned().collect(); + keys.sort(); + keys.iter() + .map(|key| -> String { layer.get(key).cloned().unwrap() }) + .collect() + }) + .collect(); +} + +/* + * Adjusts the ranks for all nodes in the graph such that all nodes v have + * rank(v) >= 0 and at least one node w has rank(w) = 0. + */ +pub fn normalize_ranks(graph: &mut Graph) { + let node_ids = graph.nodes(); + let node_ranks: Vec = node_ids + .iter() + .map(|v| { + graph + .node(v) + .unwrap_or(&GraphNode::default()) + .rank + .clone() + .unwrap_or(0) + }) + .collect(); + let min = node_ranks.iter().min().cloned().unwrap_or(0); + node_ids.iter().for_each(|node_id| { + let node_ = graph.node_mut(node_id); + if let Some(node) = node_ { + if node.rank.is_some() { + node.rank = Some(node.rank.unwrap() - min); + } + } + }) +} + +pub fn remove_empty_ranks(graph: &mut Graph) { + // Ranks may not start at 0, so we need to offset them + let nodes: Vec = graph.nodes(); + let node_ranks: Vec = nodes + .iter() + .map(|v| -> i32 { + graph + .node(v) + .cloned() + .unwrap_or(GraphNode::default()) + .rank + .unwrap_or(0) + }) + .collect(); + let offset: i32 = node_ranks.iter().min().cloned().unwrap_or(0); + + let mut layers: OrderedHashMap> = OrderedHashMap::new(); + for v in nodes.iter() { + let rank = graph + .node(v) + .unwrap_or(&GraphNode::default()) + .rank + .clone() + .unwrap_or(0) + - offset; + layers.entry(rank.clone()).or_insert(vec![]).push(v.clone()); + } + + let mut delta = 0; + let node_rank_factor = graph.graph().node_rank_factor.clone().unwrap_or(0.0) as i32; + for (i, vs) in layers.iter() { + if vs.len() == 0 && i.clone() % node_rank_factor != 0 { + delta -= 1; + } else if delta != 0 { + for v in vs.iter() { + let node_ = graph.node_mut(v); + if let Some(node) = node_ { + node.rank = Some(node.rank.unwrap_or(0) + delta.clone()) + } + } + } + } +} + +pub fn add_border_node( + graph: &mut Graph, + prefix: &str, + rank: Option<&usize>, + order: Option<&usize>, +) -> String { + let mut node = GraphNode::default(); + + if rank.is_some() { + node.rank = Some(rank.cloned().unwrap_or(0) as i32); + } + if order.is_some() { + node.order = Some(order.cloned().unwrap_or(0)); + } + + return add_dummy_node(graph, "border".to_string(), node, prefix.to_string()); +} + +pub fn max_rank(g: &Graph) -> i32 { + g.nodes() + .iter() + .map(|v| g.node(v).as_ref().unwrap().rank.clone().unwrap()) + .max() + .unwrap_or(0) +} + +#[derive(Debug, Clone)] +pub struct PartitionResponse { + pub lhs: Vec, + pub rhs: Vec, +} +/* + * Partition a collection into two groups: `lhs` and `rhs`. If the supplied + * function returns true for an entry it goes into `lhs`. Otherwise it goes + * into `rhs. + */ +pub fn partition( + collection: &Vec, + fn_: Box bool>, +) -> PartitionResponse { + let mut result: PartitionResponse = PartitionResponse { + lhs: vec![], + rhs: vec![], + }; + + collection.iter().for_each(|val| { + if fn_(val) { + result.lhs.push(val.clone()); + } else { + result.rhs.push(val.clone()); + } + }); + + return result; +} diff --git a/patches/dagre_rust/src/lib.rs b/patches/dagre_rust/src/lib.rs new file mode 100644 index 0000000..5bfb551 --- /dev/null +++ b/patches/dagre_rust/src/lib.rs @@ -0,0 +1,131 @@ +pub mod layout; + +use crate::layout::add_border_segments::BorderTypeName; +use graphlib_rust::Edge; +use ordered_hashmap::OrderedHashMap; + +#[allow(dead_code)] +#[derive(Debug, Clone, Default)] +pub struct GraphNode { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, + pub class: Option, + pub label: Option, + pub padding: Option, + pub padding_x: Option, + pub padding_y: Option, + pub rx: Option, + pub ry: Option, + pub shape: Option, + pub dummy: Option, + pub rank: Option, + pub min_rank: Option, + pub max_rank: Option, + pub order: Option, + pub border_top: Option, + pub border_bottom: Option, + pub border_left: Option>, + pub border_right: Option>, + pub border_left_: Option, + pub border_right_: Option, + pub low: Option, + pub lim: Option, + pub parent: Option, + pub e: Option, + pub edge_label: Option, + pub edge_obj: Option, + pub labelpos: Option, + pub border_type: Option, + pub self_edges: Vec<(Edge, GraphEdge)>, +} + +#[derive(Debug, Clone, Default)] +pub struct GraphEdgePoint { + pub x: f32, + pub y: f32, +} + +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct GraphEdge { + pub forward_name: Option, + pub reversed: Option, + pub minlen: Option, + pub weight: Option, + pub width: Option, + pub height: Option, + pub label_rank: Option, + pub labeloffset: Option, + pub labelpos: Option, + pub nesting_edge: Option, + pub cutvalue: Option, + pub points: Option>, + pub x: f32, + pub y: f32, +} + +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct GraphConfig { + pub width: f32, + pub height: f32, + + pub nodesep: Option, // default 50 + pub edgesep: Option, // default 20 + pub ranksep: Option, // default 50 + pub marginx: Option, // default 0 + pub marginy: Option, // default 0 + pub rankdir: Option, // lr, lr, tb, bt // default tb + pub acyclicer: Option, // greedy, dfs, unknown-should-still-work + pub ranker: Option, // "longest-path", "tight-tree", "network-simplex", "unknown-should-still-work" + pub align: Option, + pub nesting_root: Option, // id of dummy nesting root + pub root: Option, + pub node_rank_factor: Option, // default 0 + pub dummy_chains: Option>, +} + +impl Default for GraphConfig { + fn default() -> Self { + Self { + width: 0.0, + height: 0.0, + nodesep: Some(50.0), + edgesep: Some(20.0), + ranksep: Some(50.0), + marginx: None, + marginy: None, + rankdir: Some("tb".to_string()), + acyclicer: None, + ranker: None, + align: None, + nesting_root: None, + root: None, + node_rank_factor: None, + dummy_chains: None, + } + } +} + +impl Default for GraphEdge { + fn default() -> Self { + Self { + forward_name: None, + reversed: None, + minlen: Some(1.0), + weight: Some(1.0), + width: Some(0.0), + height: Some(0.0), + label_rank: None, + labeloffset: Some(0.0), + labelpos: Some("r".to_string()), + nesting_edge: None, + cutvalue: None, + points: None, + x: 0.0, + y: 0.0, + } + } +}