fix(mermaid): switch to crates.io and patch dagre_rust panic
Switch mermaid-rs-renderer from git dependency to crates.io 0.1. Vendor and patch dagre_rust 0.0.5 to fix upstream panic in remove_edge_label_proxies() where unwrap() is called on None when processing edge label proxies without edge references. This enables sequence diagrams and state diagrams that previously crashed the build.
This commit is contained in:
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
1
patches/dagre_rust/.cargo-ok
Normal file
1
patches/dagre_rust/.cargo-ok
Normal file
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
6
patches/dagre_rust/.cargo_vcs_info.json
Normal file
6
patches/dagre_rust/.cargo_vcs_info.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "62c894d556f29ddc2c4f97047e511907290a56d3"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
2
patches/dagre_rust/.gitignore
vendored
Normal file
2
patches/dagre_rust/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.idea
|
||||
28
patches/dagre_rust/Cargo.toml
Normal file
28
patches/dagre_rust/Cargo.toml
Normal file
@@ -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 <r3alst@gmail.com>"]
|
||||
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"
|
||||
14
patches/dagre_rust/Cargo.toml.orig
generated
Normal file
14
patches/dagre_rust/Cargo.toml.orig
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "dagre_rust"
|
||||
version = "0.0.5"
|
||||
authors = ["Ameer Hamza <r3alst@gmail.com>"]
|
||||
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"
|
||||
201
patches/dagre_rust/LICENCE
Normal file
201
patches/dagre_rust/LICENCE
Normal file
@@ -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.
|
||||
9
patches/dagre_rust/README.md
Normal file
9
patches/dagre_rust/README.md
Normal file
@@ -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.
|
||||
85
patches/dagre_rust/src/layout/acyclic.rs
Normal file
85
patches/dagre_rust/src/layout/acyclic.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let mut fas: Option<Vec<Edge>> = 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<GraphConfig, GraphNode, GraphEdge>) -> Vec<Edge> {
|
||||
let mut fas: Vec<Edge> = vec![];
|
||||
let mut stack: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
|
||||
fn dfs(
|
||||
node_id: String,
|
||||
stack: &mut OrderedHashMap<String, bool>,
|
||||
visited: &mut OrderedHashMap<String, bool>,
|
||||
graph: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
fas: &mut Vec<Edge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
72
patches/dagre_rust/src/layout/add_border_segments.rs
Normal file
72
patches/dagre_rust/src/layout/add_border_segments.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
fn dfs(v: &String, g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
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()));
|
||||
}
|
||||
89
patches/dagre_rust/src/layout/coordinate_system.rs
Normal file
89
patches/dagre_rust/src/layout/coordinate_system.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::Graph;
|
||||
|
||||
pub fn adjust(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
623
patches/dagre_rust/src/layout/mod.rs
Normal file
623
patches/dagre_rust/src/layout/mod.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layout_graph: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
|
||||
let mut g: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<i32> = border_left.keys().cloned().collect();
|
||||
l_keys.sort();
|
||||
let border_right = node.border_right.clone().unwrap();
|
||||
let mut r_keys: Vec<i32> = 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
make_space_for_edge_labels(graph);
|
||||
remove_self_edges(graph);
|
||||
acyclic::run(graph);
|
||||
nesting_graph::run(graph);
|
||||
// calculating ranks
|
||||
let mut nc_graph: Graph<GraphConfig, GraphNode, GraphEdge> = 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);
|
||||
}
|
||||
213
patches/dagre_rust/src/layout/nesting_graph.rs
Normal file
213
patches/dagre_rust/src/layout/nesting_graph.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) -> OrderedHashMap<String, usize> {
|
||||
let mut depths: OrderedHashMap<String, usize> = OrderedHashMap::new();
|
||||
|
||||
fn dfs(
|
||||
node_id: String,
|
||||
depth: usize,
|
||||
depths: &mut OrderedHashMap<String, usize>,
|
||||
graph: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
root: &String,
|
||||
node_sep: &f32,
|
||||
weight: &f32,
|
||||
height: &usize,
|
||||
depths: &OrderedHashMap<String, usize>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>) -> 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
patches/dagre_rust/src/layout/normalize/mod.rs
Normal file
132
patches/dagre_rust/src/layout/normalize/mod.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>, 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::Graph;
|
||||
use ordered_hashmap::OrderedHashMap;
|
||||
|
||||
pub fn add_subgraph_constraints(
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
cg: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
vs: &Vec<String>,
|
||||
) {
|
||||
let mut prev: OrderedHashMap<String, String> = OrderedHashMap::new();
|
||||
let mut _root_prev: Option<String> = None;
|
||||
|
||||
vs.iter().for_each(|v| {
|
||||
let mut child = g.parent(v).cloned();
|
||||
let mut _parent: Option<String> = None;
|
||||
let mut _prev_child: Option<String> = 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
44
patches/dagre_rust/src/layout/order/barycenter.rs
Normal file
44
patches/dagre_rust/src/layout/order/barycenter.rs
Normal file
@@ -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<f32>,
|
||||
pub weight: Option<f32>,
|
||||
}
|
||||
|
||||
pub fn barycenter(
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
movable: &Vec<String>,
|
||||
) -> Vec<Barycenter> {
|
||||
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()
|
||||
}
|
||||
112
patches/dagre_rust/src/layout/order/build_layer_graph.rs
Normal file
112
patches/dagre_rust/src/layout/order/build_layer_graph.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>,
|
||||
rank: &i32,
|
||||
relationship: GraphRelationship,
|
||||
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
|
||||
let root = create_root_node(g);
|
||||
let mut result: Graph<GraphConfig, GraphNode, GraphEdge> = 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<Edge> = 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<GraphConfig, GraphNode, GraphEdge>) -> String {
|
||||
let mut v = format!("_root{}", unique_id());
|
||||
while g.has_node(&v) {
|
||||
v = format!("_root{}", unique_id());
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
104
patches/dagre_rust/src/layout/order/cross_count.rs
Normal file
104
patches/dagre_rust/src/layout/order/cross_count.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &mut Vec<Vec<String>>,
|
||||
) -> 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &mut Vec<Vec<String>>,
|
||||
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<String, usize> = 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::<Vec<Vec<(usize, f32)>>>()
|
||||
.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<usize> = 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
|
||||
}
|
||||
68
patches/dagre_rust/src/layout/order/init_order.rs
Normal file
68
patches/dagre_rust/src/layout/order/init_order.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) -> Vec<Vec<String>> {
|
||||
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
let mut simple_nodes: Vec<String> = 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<Vec<String>> = (0..=max_rank).map(|_| -> Vec<String> { vec![] }).collect();
|
||||
|
||||
fn dfs(
|
||||
v: &String,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
visited: &mut OrderedHashMap<String, bool>,
|
||||
layers: &mut Vec<Vec<String>>,
|
||||
) {
|
||||
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<String> = 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;
|
||||
}
|
||||
110
patches/dagre_rust/src/layout/order/mod.rs
Normal file
110
patches/dagre_rust/src/layout/order/mod.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let max_rank = util::max_rank(g);
|
||||
let down_layer_ranks: Vec<i32> = (1..=max_rank).collect();
|
||||
let up_layer_ranks: Vec<i32> = (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<String>> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
ranks: &Vec<i32>,
|
||||
relationship: GraphRelationship,
|
||||
) -> Vec<Graph<GraphConfig, GraphNode, GraphEdge>> {
|
||||
return ranks
|
||||
.iter()
|
||||
.map(|rank| build_layer_graph(g, rank, relationship))
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn sweep_layer_graphs(
|
||||
layer_graphs: &mut Vec<Graph<GraphConfig, GraphNode, GraphEdge>>,
|
||||
bias_right: bool,
|
||||
) {
|
||||
let mut cg: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>, layering: &Vec<Vec<String>>) {
|
||||
for layer in layering {
|
||||
for (i, v) in layer.iter().enumerate() {
|
||||
let node_label = g.node_mut(v).unwrap();
|
||||
node_label.order = Some(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
188
patches/dagre_rust/src/layout/order/resolve_conflicts.rs
Normal file
188
patches/dagre_rust/src/layout/order/resolve_conflicts.rs
Normal file
@@ -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<ResolvedBaryEntry>,
|
||||
pub _out: Vec<ResolvedBaryEntry>,
|
||||
pub vs: Vec<String>,
|
||||
pub i: usize,
|
||||
pub barycenter: Option<f32>,
|
||||
pub weight: Option<f32>,
|
||||
pub merged: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn resolve_conflicts(
|
||||
entries: &Vec<Barycenter>,
|
||||
cg: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Vec<ResolvedBaryEntry> {
|
||||
let mut mapped_entries: OrderedHashMap<String, ResolvedBaryEntry> = 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<ResolvedBaryEntry> = 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<ResolvedBaryEntry>) -> Vec<ResolvedBaryEntry> {
|
||||
let mut entries: Vec<ResolvedBaryEntry> = vec![];
|
||||
let mut source_hash: OrderedHashMap<usize, ResolvedBaryEntry> = 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<usize, ResolvedBaryEntry>,
|
||||
) {
|
||||
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<usize, ResolvedBaryEntry>,
|
||||
source_set: &mut Vec<ResolvedBaryEntry>,
|
||||
) {
|
||||
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<usize, ResolvedBaryEntry>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
78
patches/dagre_rust/src/layout/order/sort.rs
Normal file
78
patches/dagre_rust/src/layout/order/sort.rs
Normal file
@@ -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<ResolvedBaryEntry>, bias_right: &bool) -> SubgraphResult {
|
||||
let parts: PartitionResponse<ResolvedBaryEntry> = 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<String> = 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<String>,
|
||||
unsortable: &mut Vec<ResolvedBaryEntry>,
|
||||
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)
|
||||
};
|
||||
}
|
||||
118
patches/dagre_rust/src/layout/order/sort_subgraph.rs
Normal file
118
patches/dagre_rust/src/layout/order/sort_subgraph.rs
Normal file
@@ -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<String>,
|
||||
pub barycenter: f32,
|
||||
pub weight: f32,
|
||||
}
|
||||
|
||||
pub fn sort_subgraph(
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
v: &String,
|
||||
cg: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<String, SubgraphResult> = 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<ResolvedBaryEntry>,
|
||||
subgraphs: &OrderedHashMap<String, SubgraphResult>,
|
||||
) {
|
||||
entries.iter_mut().for_each(|entry| {
|
||||
let mut vs: Vec<String> = 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());
|
||||
}
|
||||
}
|
||||
141
patches/dagre_rust/src/layout/parent_dummy_chains.rs
Normal file
141
patches/dagre_rust/src/layout/parent_dummy_chains.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let post_order_nums: OrderedHashMap<String, (i32, i32)> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
post_order_nums: &OrderedHashMap<String, (i32, i32)>,
|
||||
v: &String,
|
||||
w: &String,
|
||||
) -> (Vec<String>, String) {
|
||||
let mut v_path: Vec<String> = vec![];
|
||||
let mut w_path: Vec<String> = 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<GraphConfig, GraphNode, GraphEdge>) -> OrderedHashMap<String, (i32, i32)> {
|
||||
let mut result: OrderedHashMap<String, (i32, i32)> = OrderedHashMap::new();
|
||||
let mut lim = 0;
|
||||
|
||||
fn dfs(
|
||||
v: &String,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
lim: &mut i32,
|
||||
result: &mut OrderedHashMap<String, (i32, i32)>,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
718
patches/dagre_rust/src/layout/position/bk.rs
Normal file
718
patches/dagre_rust/src/layout/position/bk.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &Vec<Vec<String>>,
|
||||
) -> OrderedHashMap<String, OrderedHashMap<String, bool>> {
|
||||
let mut conflicts = OrderedHashMap::new();
|
||||
|
||||
fn visit_layer(
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
prev_layer: &Vec<String>,
|
||||
layer: &Vec<String>,
|
||||
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
|
||||
) {
|
||||
// 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &Vec<Vec<String>>,
|
||||
) -> OrderedHashMap<String, OrderedHashMap<String, bool>> {
|
||||
let mut conflicts: OrderedHashMap<String, OrderedHashMap<String, bool>> = OrderedHashMap::new();
|
||||
|
||||
fn scan(
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
south: &Vec<String>,
|
||||
south_pos: &usize,
|
||||
south_end: &usize,
|
||||
prev_north_border: &i32,
|
||||
next_north_border: &i32,
|
||||
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
north: &Vec<String>,
|
||||
south: &Vec<String>,
|
||||
conflicts: &mut OrderedHashMap<String, OrderedHashMap<String, bool>>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
v: &String,
|
||||
) -> Option<String> {
|
||||
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<String, OrderedHashMap<String, bool>>,
|
||||
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<String, OrderedHashMap<String, bool>>,
|
||||
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<String, bool> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &Vec<Vec<String>>,
|
||||
conflicts: &OrderedHashMap<String, OrderedHashMap<String, bool>>,
|
||||
neighbor_fn: Box<dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &String) -> Vec<String>>,
|
||||
) -> (OrderedHashMap<String, String>, Vec<String>) {
|
||||
let mut root: OrderedHashMap<String, String> = OrderedHashMap::new();
|
||||
let mut align: OrderedHashMap<String, String> = OrderedHashMap::new();
|
||||
let mut pos: OrderedHashMap<String, usize> = 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<String> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &Vec<Vec<String>>,
|
||||
root: &OrderedHashMap<String, String>,
|
||||
align: &Vec<String>,
|
||||
reverse_sep: bool,
|
||||
) -> OrderedHashMap<String, f32> {
|
||||
// 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<String, f32> = OrderedHashMap::new();
|
||||
let block_g: Graph<GraphOption, String, f32> =
|
||||
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<String, f32>,
|
||||
&Graph<GraphOption, String, f32>,
|
||||
&Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
&BorderTypeName,
|
||||
),
|
||||
next_nodes_func: Box<dyn Fn(&Graph<GraphOption, String, f32>, &String) -> Vec<String>>,
|
||||
block_g: &Graph<GraphOption, String, f32>,
|
||||
xs: &mut OrderedHashMap<String, f32>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
border_type: &BorderTypeName,
|
||||
) {
|
||||
let mut stack = block_g.nodes();
|
||||
let mut elem = stack.pop();
|
||||
let mut visited: OrderedHashMap<String, bool> = 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<String>));
|
||||
}
|
||||
|
||||
elem = stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// First pass, assign smallest coordinates
|
||||
fn pass1(
|
||||
elem: &String,
|
||||
xs: &mut OrderedHashMap<String, f32>,
|
||||
block_g: &Graph<GraphOption, String, f32>,
|
||||
_g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
_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<String, f32>,
|
||||
block_g: &Graph<GraphOption, String, f32>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<String> { block_g.predecessors(v).unwrap_or(vec![]) }),
|
||||
&block_g,
|
||||
&mut xs,
|
||||
g,
|
||||
&border_type,
|
||||
);
|
||||
|
||||
iterate(
|
||||
pass2,
|
||||
Box::new(|block_g, v| -> Vec<String> { 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &Vec<Vec<String>>,
|
||||
root: &OrderedHashMap<String, String>,
|
||||
reverse_sep: bool,
|
||||
) -> Graph<GraphOption, String, f32> {
|
||||
let mut block_graph: Graph<GraphOption, String, f32> = Graph::new(None);
|
||||
let graph_label = g.graph();
|
||||
let sep_fn: Box<dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &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<String> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
xss: &'a OrderedHashMap<String, OrderedHashMap<String, f32>>,
|
||||
) -> &'a OrderedHashMap<String, f32> {
|
||||
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<String, OrderedHashMap<String, f32>>,
|
||||
align_to: &OrderedHashMap<String, f32>,
|
||||
) {
|
||||
let align_to_vals: Vec<f32> = 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<f32> = 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<String, OrderedHashMap<String, f32>>,
|
||||
align: Option<String>,
|
||||
) -> OrderedHashMap<String, f32> {
|
||||
let mut xss_clone = xss.clone();
|
||||
if let Some(ul) = xss_clone.get_mut(&"ul".to_string()) {
|
||||
let keys: Vec<String> = ul.keys().cloned().collect();
|
||||
keys.iter().for_each(|v| {
|
||||
if align.is_some() {
|
||||
let empty_hash: OrderedHashMap<String, f32> = 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<f32> = 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<GraphConfig, GraphNode, GraphEdge>) -> OrderedHashMap<String, f32> {
|
||||
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<String, OrderedHashMap<String, f32>> = OrderedHashMap::new();
|
||||
let mut adjusted_layering: Option<Vec<Vec<String>>> = 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<GraphConfig, GraphNode, GraphEdge>, &String) -> Vec<String>,
|
||||
> = if vert == &"u" {
|
||||
Box::new(
|
||||
|g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String| -> Vec<String> {
|
||||
g.predecessors(v).unwrap_or(vec![])
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Box::new(
|
||||
|g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String| -> Vec<String> {
|
||||
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<String, f32> = 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<dyn Fn(&Graph<GraphConfig, GraphNode, GraphEdge>, &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<GraphConfig, GraphNode, GraphEdge>, 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<GraphConfig, GraphNode, GraphEdge>, v: &String) -> f32 {
|
||||
g.node(v).unwrap().width
|
||||
}
|
||||
36
patches/dagre_rust/src/layout/position/mod.rs
Normal file
36
patches/dagre_rust/src/layout/position/mod.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let mut ncg: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
142
patches/dagre_rust/src/layout/rank/feasible_tree.rs
Normal file
142
patches/dagre_rust/src/layout/rank/feasible_tree.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
|
||||
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = 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<Edge>;
|
||||
let mut delta: Option<i32>;
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> usize {
|
||||
fn dfs(
|
||||
v: &String,
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Option<Edge> {
|
||||
let edges = g.edges();
|
||||
let result = edges
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let mut e_: Option<i32> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
patches/dagre_rust/src/layout/rank/mod.rs
Normal file
53
patches/dagre_rust/src/layout/rank/mod.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
longest_path(g);
|
||||
feasible_tree(g);
|
||||
}
|
||||
343
patches/dagre_rust/src/layout/rank/network_simplex.rs
Normal file
343
patches/dagre_rust/src/layout/rank/network_simplex.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
simplify_ref(g); // =g = simplify(g);
|
||||
longest_path(g); // =init_rank
|
||||
|
||||
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>, root_: Option<String>) {
|
||||
let mut root = tree.nodes().first().cloned().unwrap_or("".to_string());
|
||||
if root_.is_some() {
|
||||
root = root_.unwrap();
|
||||
}
|
||||
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
dfs_assign_low_lim(tree, &mut visited, 1, &root, None);
|
||||
}
|
||||
|
||||
fn dfs_assign_low_lim(
|
||||
tree: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
visited: &mut OrderedHashMap<String, bool>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>) -> Option<Edge> {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
edge: &Edge,
|
||||
) -> Option<Edge> {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>, 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
|
||||
}
|
||||
99
patches/dagre_rust/src/layout/rank/util.rs
Normal file
99
patches/dagre_rust/src/layout/rank/util.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
|
||||
fn dfs(
|
||||
v: &String,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
visited: &mut OrderedHashMap<String, bool>,
|
||||
) -> 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<i32> = 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<GraphConfig, GraphNode, GraphEdge>, 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;
|
||||
}
|
||||
346
patches/dagre_rust/src/layout/util.rs
Normal file
346
patches/dagre_rust/src/layout/util.rs
Normal file
@@ -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<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
|
||||
let mut simplified: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
|
||||
let mut simplified: Graph<GraphConfig, GraphNode, GraphEdge> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
destination: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
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<GraphConfig, GraphNode, GraphEdge>) -> Vec<Vec<String>> {
|
||||
let mut layering: Vec<OrderedHashMap<usize, String>> =
|
||||
(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<usize, String> = layering.get_mut(rank).unwrap();
|
||||
layer.insert(node.order.unwrap_or(0), v.clone());
|
||||
});
|
||||
|
||||
return layering
|
||||
.into_iter()
|
||||
.map(|layer| -> Vec<String> {
|
||||
let mut keys: Vec<usize> = 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let node_ids = graph.nodes();
|
||||
let node_ranks: Vec<i32> = 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<GraphConfig, GraphNode, GraphEdge>) {
|
||||
// Ranks may not start at 0, so we need to offset them
|
||||
let nodes: Vec<String> = graph.nodes();
|
||||
let node_ranks: Vec<i32> = 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<i32, Vec<String>> = 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<GraphConfig, GraphNode, GraphEdge>,
|
||||
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<GraphConfig, GraphNode, GraphEdge>) -> 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<V> {
|
||||
pub lhs: Vec<V>,
|
||||
pub rhs: Vec<V>,
|
||||
}
|
||||
/*
|
||||
* 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<V: Clone>(
|
||||
collection: &Vec<V>,
|
||||
fn_: Box<dyn Fn(&V) -> bool>,
|
||||
) -> PartitionResponse<V> {
|
||||
let mut result: PartitionResponse<V> = 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;
|
||||
}
|
||||
131
patches/dagre_rust/src/lib.rs
Normal file
131
patches/dagre_rust/src/lib.rs
Normal file
@@ -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<String>,
|
||||
pub label: Option<GraphEdge>,
|
||||
pub padding: Option<f32>,
|
||||
pub padding_x: Option<f32>,
|
||||
pub padding_y: Option<f32>,
|
||||
pub rx: Option<f32>,
|
||||
pub ry: Option<f32>,
|
||||
pub shape: Option<String>,
|
||||
pub dummy: Option<String>,
|
||||
pub rank: Option<i32>,
|
||||
pub min_rank: Option<i32>,
|
||||
pub max_rank: Option<i32>,
|
||||
pub order: Option<usize>,
|
||||
pub border_top: Option<String>,
|
||||
pub border_bottom: Option<String>,
|
||||
pub border_left: Option<OrderedHashMap<i32, String>>,
|
||||
pub border_right: Option<OrderedHashMap<i32, String>>,
|
||||
pub border_left_: Option<String>,
|
||||
pub border_right_: Option<String>,
|
||||
pub low: Option<usize>,
|
||||
pub lim: Option<usize>,
|
||||
pub parent: Option<String>,
|
||||
pub e: Option<Edge>,
|
||||
pub edge_label: Option<GraphEdge>,
|
||||
pub edge_obj: Option<Edge>,
|
||||
pub labelpos: Option<String>,
|
||||
pub border_type: Option<BorderTypeName>,
|
||||
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<String>,
|
||||
pub reversed: Option<bool>,
|
||||
pub minlen: Option<f32>,
|
||||
pub weight: Option<f32>,
|
||||
pub width: Option<f32>,
|
||||
pub height: Option<f32>,
|
||||
pub label_rank: Option<i32>,
|
||||
pub labeloffset: Option<f32>,
|
||||
pub labelpos: Option<String>,
|
||||
pub nesting_edge: Option<bool>,
|
||||
pub cutvalue: Option<f32>,
|
||||
pub points: Option<Vec<GraphEdgePoint>>,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphConfig {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
|
||||
pub nodesep: Option<f32>, // default 50
|
||||
pub edgesep: Option<f32>, // default 20
|
||||
pub ranksep: Option<f32>, // default 50
|
||||
pub marginx: Option<f32>, // default 0
|
||||
pub marginy: Option<f32>, // default 0
|
||||
pub rankdir: Option<String>, // lr, lr, tb, bt // default tb
|
||||
pub acyclicer: Option<String>, // greedy, dfs, unknown-should-still-work
|
||||
pub ranker: Option<String>, // "longest-path", "tight-tree", "network-simplex", "unknown-should-still-work"
|
||||
pub align: Option<String>,
|
||||
pub nesting_root: Option<String>, // id of dummy nesting root
|
||||
pub root: Option<String>,
|
||||
pub node_rank_factor: Option<f32>, // default 0
|
||||
pub dummy_chains: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user