Publish harness and TUI open-source
initial sync from the monorepo
This commit is contained in:
commit
c68e39f604
2734 changed files with 1437016 additions and 0 deletions
56
third_party/dagre_rust/Cargo.toml
vendored
Normal file
56
third_party/dagre_rust/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Vendored third-party crate.
|
||||
#
|
||||
# Upstream: dagre_rust 0.0.5 (Apache-2.0), as re-published in
|
||||
# warpdotdev/mermaid-to-svg `crates/dagre_rust` ("vendored from 0.0.5
|
||||
# with fixes" per upstream Cargo.toml).
|
||||
# Source of truth: https://github.com/warpdotdev/mermaid-to-svg (rev
|
||||
# 40cecf2be376e47e15053eadbfb782a531777420), crates/dagre_rust/.
|
||||
# Base crate: https://crates.io/crates/dagre_rust/0.0.5
|
||||
#
|
||||
# Why vendored: the Rust port of dagre.js (the same layout engine mermaid.js
|
||||
# uses). It is the layout core of the Warp `mermaid-to-svg` engine, which renders
|
||||
# untrusted model output, so vendoring gives a full audit surface and immunity to
|
||||
# upstream yanks. See third_party/mermaid-to-svg and crates/codegen/xai-grok-mermaid.
|
||||
#
|
||||
# ============================================================================
|
||||
# VENDORING NOTES — re-apply / re-bless ALL of the following on every upgrade:
|
||||
# ============================================================================
|
||||
# Local modifications to the upstream source:
|
||||
# 1. `cargo fmt` (rustfmt) applied so the crate satisfies the workspace fmt
|
||||
# gate; no semantic change. Otherwise the library source (`src/**`, incl. one
|
||||
# in-source unit test in `src/layout/util.rs`) is the `mermaid-to-svg`
|
||||
# in-repo copy; the `graphlib_rust` / `ordered_hashmap` deps are repointed to
|
||||
# the sibling vendored crates.
|
||||
# 2. UNSAFE REMOVAL (src/layout/util.rs): the upstream unique-id counter was a
|
||||
# `static mut UNIQUE_STARTER` incremented inside an `unsafe` block with no
|
||||
# synchronization — a data race whenever the engine renders on more than one
|
||||
# thread (e.g. the parallel `cargo test` suite). Replaced with a
|
||||
# `std::sync::atomic::AtomicUsize` (`fetch_add`); behaviour-preserving (still
|
||||
# hands out monotonic unique ids) and it removes the only `unsafe` here.
|
||||
#
|
||||
# Re-audit checklist:
|
||||
# - No `unsafe` remains (the `static mut` counter became an `AtomicUsize`; see
|
||||
# local modification 2), and no filesystem / env / network I/O in the source.
|
||||
[package]
|
||||
name = "dagre_rust"
|
||||
version = "0.0.5"
|
||||
edition = "2021"
|
||||
description = "Dagre layout in Rust (vendored, library-only)"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/r3alst/dagre-rust"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "dagre_rust"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
graphlib_rust = { path = "../graphlib_rust" }
|
||||
ordered_hashmap = { path = "../ordered_hashmap" }
|
||||
|
||||
# Clippy lints local/path crates (this vendored crate is one) under the
|
||||
# workspace's `-D warnings`. We do not restyle upstream third-party code, so
|
||||
# allow clippy here; our own crate (xai-grok-mermaid) is still fully linted.
|
||||
[lints.clippy]
|
||||
all = { level = "allow", priority = -1 }
|
||||
201
third_party/dagre_rust/LICENCE
vendored
Normal file
201
third_party/dagre_rust/LICENCE
vendored
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.
|
||||
87
third_party/dagre_rust/src/layout/acyclic.rs
vendored
Normal file
87
third_party/dagre_rust/src/layout/acyclic.rs
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
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 Some(edge) = g.edge_mut_with_obj(&e) else {
|
||||
continue;
|
||||
};
|
||||
if edge.reversed.clone().unwrap_or(false) {
|
||||
let forward_name = edge.forward_name.clone();
|
||||
let mut label = edge.clone();
|
||||
label.reversed = None;
|
||||
label.forward_name = None;
|
||||
let _ = g.set_edge(&e.w, &e.v, Some(label), forward_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
third_party/dagre_rust/src/layout/add_border_segments.rs
vendored
Normal file
73
third_party/dagre_rust/src/layout/add_border_segments.rs
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
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);
|
||||
add_border_node(g, BorderTypeName::BorderRight, "_br", 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
third_party/dagre_rust/src/layout/coordinate_system.rs
vendored
Normal file
89
third_party/dagre_rust/src/layout/coordinate_system.rs
vendored
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;
|
||||
});
|
||||
}
|
||||
645
third_party/dagre_rust/src/layout/mod.rs
vendored
Normal file
645
third_party/dagre_rust/src/layout/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,645 @@
|
|||
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.w);
|
||||
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);
|
||||
label.width = edge.width.unwrap_or(0.0);
|
||||
label.height = edge.height.unwrap_or(0.0);
|
||||
label.labelpos = edge.labelpos.clone();
|
||||
label.e = Some(e.clone());
|
||||
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.width.unwrap_or(0.0) > 0.0 && edge.height.unwrap_or(0.0) > 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();
|
||||
let has_label = edge.width.unwrap_or(0.0) > 0.0 && edge.height.unwrap_or(0.0) > 0.0;
|
||||
if edge.points.is_some() {
|
||||
for p in edge.points.as_mut().unwrap() {
|
||||
p.x -= min_x;
|
||||
p.y -= min_y;
|
||||
}
|
||||
}
|
||||
if has_label {
|
||||
edge.x -= min_x;
|
||||
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.as_ref().map_or(true, |p| p.is_empty()) {
|
||||
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);
|
||||
let graph_edge_ = g.edge_mut_with_obj(&node.e.clone().unwrap());
|
||||
if let Some(graph_edge) = graph_edge_ {
|
||||
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 Some(border_top) = node.border_top.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let Some(border_bottom) = node.border_bottom.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let Some(t) = g.node(border_top).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let Some(b) = g.node(border_bottom).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let Some(border_left) = node.border_left.clone() else {
|
||||
continue;
|
||||
};
|
||||
let Some(border_right) = node.border_right.clone() else {
|
||||
continue;
|
||||
};
|
||||
let mut l_keys: Vec<i32> = border_left.keys().cloned().collect();
|
||||
if l_keys.is_empty() {
|
||||
continue;
|
||||
}
|
||||
l_keys.sort();
|
||||
let mut r_keys: Vec<i32> = border_right.keys().cloned().collect();
|
||||
if r_keys.is_empty() {
|
||||
continue;
|
||||
}
|
||||
r_keys.sort();
|
||||
let Some(l_node_id) = border_left.get(&l_keys[l_keys.len() - 1]) else {
|
||||
continue;
|
||||
};
|
||||
let Some(r_node_id) = border_right.get(&r_keys[r_keys.len() - 1]) else {
|
||||
continue;
|
||||
};
|
||||
let Some(l) = g.node(l_node_id).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let Some(r) = g.node(r_node_id).cloned() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
node.width = (r.x - l.x).abs();
|
||||
node.height = (b.y - t.y).abs();
|
||||
node.x = l.x + node.width / 2.0;
|
||||
node.y = t.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
third_party/dagre_rust/src/layout/nesting_graph.rs
vendored
Normal file
213
third_party/dagre_rust/src/layout/nesting_graph.rs
vendored
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 = 2.0 * 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 = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
third_party/dagre_rust/src/layout/normalize/mod.rs
vendored
Normal file
135
third_party/dagre_rust/src/layout/normalize/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
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 Some(edge_label) = g.edge_mut_with_obj(&e) else {
|
||||
return;
|
||||
};
|
||||
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());
|
||||
let mut dummy_type = "edge".to_string();
|
||||
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.labelpos = _edge_label.labelpos.clone();
|
||||
dummy_type = "edge-label".to_string();
|
||||
}
|
||||
let dummy = add_dummy_node(g, dummy_type, 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));
|
||||
}
|
||||
}
|
||||
38
third_party/dagre_rust/src/layout/order/add_subgraph_constraints.rs
vendored
Normal file
38
third_party/dagre_rust/src/layout/order/add_subgraph_constraints.rs
vendored
Normal file
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
45
third_party/dagre_rust/src/layout/order/barycenter.rs
vendored
Normal file
45
third_party/dagre_rust/src/layout/order/barycenter.rs
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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 sum = 0.0_f64;
|
||||
let mut weight = 0.0_f64;
|
||||
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 f64;
|
||||
sum += edge_weight * (node_u.order.clone().unwrap_or(0) as f64);
|
||||
weight += edge_weight;
|
||||
});
|
||||
|
||||
return Barycenter {
|
||||
v: v.clone(),
|
||||
barycenter: Some((sum / weight) as f32),
|
||||
weight: Some(weight as f32),
|
||||
};
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
116
third_party/dagre_rust/src/layout/order/build_layer_graph.rs
vendored
Normal file
116
third_party/dagre_rust/src/layout/order/build_layer_graph.rs
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
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 in_rank = node.rank == Some(*rank);
|
||||
let in_subgraph_rank = match (node.min_rank, node.max_rank) {
|
||||
(Some(min_rank), Some(max_rank)) => min_rank <= *rank && *rank <= max_rank,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
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 in_rank || in_subgraph_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
|
||||
}
|
||||
91
third_party/dagre_rust/src/layout/order/cross_count.rs
vendored
Normal file
91
third_party/dagre_rust/src/layout/order/cross_count.rs
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use graphlib_rust::Graph;
|
||||
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
|
||||
/*
|
||||
* 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: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
layering: &Vec<Vec<String>>,
|
||||
) -> f32 {
|
||||
let mut cc = 0.0;
|
||||
for i in 1..layering.len() {
|
||||
cc += two_layer_cross_count(g, &layering[i - 1], &layering[i]);
|
||||
}
|
||||
cc
|
||||
}
|
||||
|
||||
pub fn two_layer_cross_count(
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
north_layer: &Vec<String>,
|
||||
south_layer: &Vec<String>,
|
||||
) -> f32 {
|
||||
let mut south_pos: HashMap<String, usize> = HashMap::new();
|
||||
for (i, v) in south_layer.iter().enumerate() {
|
||||
south_pos.insert(v.clone(), i);
|
||||
}
|
||||
|
||||
let mut south_entries: Vec<(usize, f32)> = vec![];
|
||||
for v in north_layer {
|
||||
let mut out_edges: Vec<(usize, f32)> = g
|
||||
.out_edges(v, None)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|e| {
|
||||
let pos = south_pos.get(&e.w)?;
|
||||
let weight = g
|
||||
.edge_with_obj(&e)
|
||||
.and_then(|edge| edge.weight)
|
||||
.unwrap_or(0.0);
|
||||
Some((*pos, weight))
|
||||
})
|
||||
.collect();
|
||||
|
||||
out_edges.sort_by(|e1, e2| e1.0.cmp(&e2.0));
|
||||
south_entries.extend(out_edges);
|
||||
}
|
||||
|
||||
let mut first_index: usize = 1;
|
||||
while first_index < south_layer.len() {
|
||||
first_index <<= 1;
|
||||
}
|
||||
let tree_size = 2 * first_index - 1;
|
||||
first_index -= 1;
|
||||
|
||||
let mut tree: Vec<f32> = vec![0.0; tree_size];
|
||||
|
||||
let mut cc = 0.0;
|
||||
for (pos, weight) in south_entries {
|
||||
let mut index = pos + first_index;
|
||||
tree[index] += weight;
|
||||
|
||||
let mut weight_sum = 0.0;
|
||||
while index > 0 {
|
||||
if index % 2 != 0 {
|
||||
weight_sum += tree[index + 1];
|
||||
}
|
||||
index = (index - 1) >> 1;
|
||||
tree[index] += weight;
|
||||
}
|
||||
cc += weight * weight_sum;
|
||||
}
|
||||
|
||||
cc
|
||||
}
|
||||
68
third_party/dagre_rust/src/layout/order/init_order.rs
vendored
Normal file
68
third_party/dagre_rust/src/layout/order/init_order.rs
vendored
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;
|
||||
}
|
||||
107
third_party/dagre_rust/src/layout/order/mod.rs
vendored
Normal file
107
third_party/dagre_rust/src/layout/order/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
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 layering = init_order(g);
|
||||
assign_order(g, &layering);
|
||||
|
||||
// Start with the init ordering as the best candidate.
|
||||
// The original dagre.js code starts with bestCC = Infinity, which means
|
||||
// the first sweep's result always replaces it. However, if the init
|
||||
// ordering already has minimal crossings, using it as the starting point
|
||||
// ensures the sweep loop doesn't accidentally select a different ordering
|
||||
// with the same crossing count but worse visual layout (e.g., a mirrored
|
||||
// ordering produced by a biased sweep).
|
||||
let init_cc = cross_count(g, &layering) as f64;
|
||||
let mut best_cc = init_cc;
|
||||
let mut best: Vec<Vec<String>> = layering.clone();
|
||||
|
||||
let mut i = 0;
|
||||
let mut last_best = 0;
|
||||
while last_best < 4 {
|
||||
if i % 2 != 0 {
|
||||
sweep_layer_graphs(g, &down_layer_ranks, GraphRelationship::InEdges, i % 4 >= 2);
|
||||
} else {
|
||||
sweep_layer_graphs(g, &up_layer_ranks, GraphRelationship::OutEdges, i % 4 >= 2);
|
||||
}
|
||||
|
||||
layering = util::build_layer_matrix(g);
|
||||
let cc = cross_count(g, &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 sweep_layer_graphs(
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
ranks: &Vec<i32>,
|
||||
relationship: GraphRelationship,
|
||||
bias_right: bool,
|
||||
) {
|
||||
let mut cg: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(None);
|
||||
|
||||
ranks.iter().for_each(|rank| {
|
||||
let lg = build_layer_graph(g, rank, relationship);
|
||||
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)| {
|
||||
if let Some(node) = g.node_mut(v) {
|
||||
node.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
173
third_party/dagre_rust/src/layout/order/resolve_conflicts.rs
vendored
Normal file
173
third_party/dagre_rust/src/layout/order/resolve_conflicts.rs
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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 std::collections::HashMap;
|
||||
|
||||
use graphlib_rust::Graph;
|
||||
|
||||
use crate::layout::order::barycenter::Barycenter;
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolvedBaryEntry {
|
||||
pub vs: Vec<String>,
|
||||
pub i: usize,
|
||||
pub barycenter: Option<f32>,
|
||||
pub weight: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ConflictEntry {
|
||||
indegree: i32,
|
||||
ins: Vec<usize>,
|
||||
outs: Vec<usize>,
|
||||
vs: Vec<String>,
|
||||
i: usize,
|
||||
barycenter: Option<f32>,
|
||||
weight: Option<f32>,
|
||||
merged: bool,
|
||||
}
|
||||
|
||||
pub fn resolve_conflicts(
|
||||
entries: &Vec<Barycenter>,
|
||||
cg: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Vec<ResolvedBaryEntry> {
|
||||
let mut id_to_idx: HashMap<String, usize> = HashMap::new();
|
||||
let mut mapped_entries: Vec<ConflictEntry> = Vec::with_capacity(entries.len());
|
||||
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
id_to_idx.insert(entry.v.clone(), i);
|
||||
mapped_entries.push(ConflictEntry {
|
||||
indegree: 0,
|
||||
ins: vec![],
|
||||
outs: vec![],
|
||||
vs: vec![entry.v.clone()],
|
||||
i,
|
||||
barycenter: entry.barycenter,
|
||||
weight: entry.weight,
|
||||
merged: false,
|
||||
});
|
||||
}
|
||||
|
||||
cg.edges().iter().for_each(|e| {
|
||||
if let (Some(&v_idx), Some(&w_idx)) = (id_to_idx.get(&e.v), id_to_idx.get(&e.w)) {
|
||||
mapped_entries[w_idx].indegree += 1;
|
||||
mapped_entries[v_idx].outs.push(w_idx);
|
||||
}
|
||||
});
|
||||
|
||||
let mut source_set: Vec<usize> = mapped_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, entry)| if entry.indegree == 0 { Some(idx) } else { None })
|
||||
.collect();
|
||||
|
||||
let mut entries_order: Vec<usize> = Vec::new();
|
||||
|
||||
while let Some(v_idx) = source_set.pop() {
|
||||
entries_order.push(v_idx);
|
||||
|
||||
let ins = mapped_entries[v_idx].ins.clone();
|
||||
ins.into_iter().rev().for_each(|u_idx| {
|
||||
handle_in(&mut mapped_entries, v_idx, u_idx);
|
||||
});
|
||||
|
||||
let outs = mapped_entries[v_idx].outs.clone();
|
||||
outs.into_iter().for_each(|w_idx| {
|
||||
handle_out(&mut mapped_entries, v_idx, w_idx, &mut source_set);
|
||||
});
|
||||
}
|
||||
|
||||
entries_order
|
||||
.into_iter()
|
||||
.filter(|idx| !mapped_entries[*idx].merged)
|
||||
.map(|idx| {
|
||||
let entry = &mapped_entries[idx];
|
||||
ResolvedBaryEntry {
|
||||
vs: entry.vs.clone(),
|
||||
i: entry.i,
|
||||
barycenter: entry.barycenter,
|
||||
weight: entry.weight,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn handle_in(entries: &mut [ConflictEntry], v_idx: usize, u_idx: usize) {
|
||||
if entries[u_idx].merged {
|
||||
return;
|
||||
}
|
||||
|
||||
let u_barycenter = entries[u_idx].barycenter;
|
||||
let v_barycenter = entries[v_idx].barycenter;
|
||||
if u_barycenter.is_none()
|
||||
|| v_barycenter.is_none()
|
||||
|| u_barycenter.unwrap() >= v_barycenter.unwrap()
|
||||
{
|
||||
merge_entries(entries, v_idx, u_idx);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_out(
|
||||
entries: &mut [ConflictEntry],
|
||||
v_idx: usize,
|
||||
w_idx: usize,
|
||||
source_set: &mut Vec<usize>,
|
||||
) {
|
||||
entries[w_idx].ins.push(v_idx);
|
||||
entries[w_idx].indegree -= 1;
|
||||
if entries[w_idx].indegree == 0 {
|
||||
source_set.push(w_idx);
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_entries(entries: &mut [ConflictEntry], target_idx: usize, source_idx: usize) {
|
||||
let mut sum = 0.0;
|
||||
let mut weight = 0.0;
|
||||
|
||||
if let (Some(target_barycenter), Some(target_weight)) =
|
||||
(entries[target_idx].barycenter, entries[target_idx].weight)
|
||||
{
|
||||
sum += target_barycenter * target_weight;
|
||||
weight += target_weight;
|
||||
}
|
||||
|
||||
if let (Some(source_barycenter), Some(source_weight)) =
|
||||
(entries[source_idx].barycenter, entries[source_idx].weight)
|
||||
{
|
||||
sum += source_barycenter * source_weight;
|
||||
weight += source_weight;
|
||||
}
|
||||
|
||||
let mut vs = entries[source_idx].vs.clone();
|
||||
vs.extend(entries[target_idx].vs.clone());
|
||||
|
||||
entries[target_idx].vs = vs;
|
||||
entries[target_idx].barycenter = Some(sum / weight);
|
||||
entries[target_idx].weight = Some(weight);
|
||||
entries[target_idx].i = std::cmp::min(entries[source_idx].i, entries[target_idx].i);
|
||||
|
||||
entries[source_idx].merged = true;
|
||||
}
|
||||
83
third_party/dagre_rust/src/layout/order/sort.rs
vendored
Normal file
83
third_party/dagre_rust/src/layout/order/sort.rs
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::layout::order::resolve_conflicts::ResolvedBaryEntry;
|
||||
use crate::layout::order::sort_subgraph::SubgraphResult;
|
||||
use crate::layout::util;
|
||||
use crate::layout::util::PartitionResponse;
|
||||
|
||||
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();
|
||||
let mut unsortable = parts.rhs.clone();
|
||||
|
||||
sortable.sort_by(|e1, e2| compare_with_bias(e1, e2, bias_right));
|
||||
unsortable.sort_by(|e1, e2| e2.i.cmp(&e1.i));
|
||||
|
||||
let mut vs: Vec<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 unsortable, vs_index);
|
||||
sortable.iter().for_each(|entry| {
|
||||
vs_index += entry.vs.len();
|
||||
vs.push(entry.vs.clone());
|
||||
let entry_weight = entry.weight.unwrap_or(0.0);
|
||||
sum += entry.barycenter.unwrap_or(0.0) * entry_weight;
|
||||
weight += entry_weight;
|
||||
vs_index = consume_unsortable(&mut vs, &mut unsortable, vs_index);
|
||||
});
|
||||
|
||||
let mut result = SubgraphResult::default();
|
||||
result.vs = vs.into_iter().flatten().collect();
|
||||
if weight != 0.0 {
|
||||
result.barycenter = Some(sum / weight);
|
||||
result.weight = Some(weight);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn consume_unsortable(
|
||||
vs: &mut Vec<Vec<String>>,
|
||||
unsortable: &mut Vec<ResolvedBaryEntry>,
|
||||
mut index: usize,
|
||||
) -> usize {
|
||||
loop {
|
||||
let last = match unsortable.last() {
|
||||
Some(last) => last,
|
||||
None => return index,
|
||||
};
|
||||
|
||||
if last.i > index {
|
||||
return index;
|
||||
}
|
||||
|
||||
let last = unsortable.pop().unwrap();
|
||||
vs.push(last.vs);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_with_bias(
|
||||
entry_v: &ResolvedBaryEntry,
|
||||
entry_w: &ResolvedBaryEntry,
|
||||
bias: &bool,
|
||||
) -> Ordering {
|
||||
let barycenter_v = entry_v.barycenter.unwrap_or(0.0);
|
||||
let barycenter_w = entry_w.barycenter.unwrap_or(0.0);
|
||||
if barycenter_v < barycenter_w {
|
||||
return Ordering::Less;
|
||||
} else if barycenter_v > barycenter_w {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
|
||||
if !bias {
|
||||
entry_v.i.cmp(&entry_w.i)
|
||||
} else {
|
||||
entry_w.i.cmp(&entry_v.i)
|
||||
}
|
||||
}
|
||||
114
third_party/dagre_rust/src/layout/order/sort_subgraph.rs
vendored
Normal file
114
third_party/dagre_rust/src/layout/order/sort_subgraph.rs
vendored
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use graphlib_rust::Graph;
|
||||
use ordered_hashmap::OrderedHashMap;
|
||||
|
||||
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};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SubgraphResult {
|
||||
pub vs: Vec<String>,
|
||||
pub barycenter: Option<f32>,
|
||||
pub weight: Option<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 = node.and_then(|n| n.border_left_.clone());
|
||||
let br = node.and_then(|n| n.border_right_.clone());
|
||||
let mut subgraphs: OrderedHashMap<String, SubgraphResult> = OrderedHashMap::new();
|
||||
|
||||
if let (Some(bl_), Some(br_)) = (bl.as_ref(), br.as_ref()) {
|
||||
movable = movable
|
||||
.into_iter()
|
||||
.filter(|w| w != bl_ && w != br_)
|
||||
.collect();
|
||||
}
|
||||
|
||||
let mut barycenters = barycenter(g, &movable);
|
||||
barycenters.iter_mut().for_each(|entry| {
|
||||
if !g.children(&entry.v).is_empty() {
|
||||
let subgraph_result = sort_subgraph(g, &entry.v, cg, bias_right);
|
||||
subgraphs.insert(entry.v.clone(), subgraph_result.clone());
|
||||
if subgraph_result.barycenter.is_some() {
|
||||
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 let (Some(bl_), Some(br_)) = (bl, br) {
|
||||
let mut vs: Vec<String> = Vec::with_capacity(result.vs.len() + 2);
|
||||
vs.push(bl_.clone());
|
||||
vs.extend(result.vs.clone());
|
||||
vs.push(br_.clone());
|
||||
result.vs = vs;
|
||||
|
||||
let bl_preds = g.predecessors(&bl_).unwrap_or_default();
|
||||
if !bl_preds.is_empty() {
|
||||
let br_preds = g.predecessors(&br_).unwrap_or_default();
|
||||
if !br_preds.is_empty() {
|
||||
let bl_pred = g.node(&bl_preds[0]).unwrap();
|
||||
let br_pred = g.node(&br_preds[0]).unwrap();
|
||||
let bl_pred_order = bl_pred.order.unwrap_or(0) as f32;
|
||||
let br_pred_order = br_pred.order.unwrap_or(0) as f32;
|
||||
|
||||
let result_barycenter = result.barycenter.unwrap_or(0.0);
|
||||
let result_weight = result.weight.unwrap_or(0.0);
|
||||
result.barycenter = Some(
|
||||
(result_barycenter * result_weight + bl_pred_order + br_pred_order)
|
||||
/ (result_weight + 2.0),
|
||||
);
|
||||
result.weight = Some(result_weight + 2.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 let Some(subgraph) = subgraphs.get(v) {
|
||||
vs.extend(subgraph.vs.clone());
|
||||
return;
|
||||
}
|
||||
vs.push(v.clone());
|
||||
});
|
||||
|
||||
entry.vs = vs;
|
||||
});
|
||||
}
|
||||
|
||||
fn merge_barycenters(target: &mut Barycenter, other: &SubgraphResult) {
|
||||
let (other_barycenter, other_weight) = match (other.barycenter, other.weight) {
|
||||
(Some(barycenter), Some(weight)) => (barycenter, weight),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let (Some(target_barycenter), Some(target_weight)) = (target.barycenter, target.weight) {
|
||||
target.barycenter = Some(
|
||||
(target_barycenter * target_weight + other_barycenter * other_weight)
|
||||
/ (target_weight + other_weight),
|
||||
);
|
||||
target.weight = Some(target_weight + other_weight);
|
||||
} else {
|
||||
target.barycenter = Some(other_barycenter);
|
||||
target.weight = Some(other_weight);
|
||||
}
|
||||
}
|
||||
154
third_party/dagre_rust/src/layout/parent_dummy_chains.rs
vendored
Normal file
154
third_party/dagre_rust/src/layout/parent_dummy_chains.rs
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
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![]);
|
||||
|
||||
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;
|
||||
let mut path_v = path.get(path_idx).cloned().unwrap_or(lca.clone());
|
||||
let mut ascending = true;
|
||||
|
||||
while v != edge_obj.w {
|
||||
node = g.node(&v).unwrap();
|
||||
let node_rank = node.rank.unwrap_or(0);
|
||||
|
||||
if ascending {
|
||||
loop {
|
||||
path_v = path.get(path_idx).cloned().unwrap_or(lca.clone());
|
||||
if path_v == lca {
|
||||
ascending = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let Some(path_v_id) = path_v.as_ref() else {
|
||||
break;
|
||||
};
|
||||
let max_rank = g.node(path_v_id).unwrap().max_rank.unwrap_or(0);
|
||||
if max_rank < node_rank {
|
||||
path_idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !ascending {
|
||||
while path_idx < path.len().saturating_sub(1) {
|
||||
let Some(next) = path.get(path_idx + 1).cloned() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let Some(next_id) = next.as_ref() else {
|
||||
break;
|
||||
};
|
||||
|
||||
if g.node(next_id).unwrap().min_rank.unwrap_or(0) <= node_rank {
|
||||
path_idx += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
path_v = path.get(path_idx).cloned().unwrap_or(lca.clone());
|
||||
}
|
||||
|
||||
let _ = g.set_parent(&v, path_v.clone());
|
||||
|
||||
let Some(next) = g.successors(&v).unwrap_or_default().first().cloned() else {
|
||||
break;
|
||||
};
|
||||
v = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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<Option<String>>, Option<String>) {
|
||||
let mut v_path: Vec<Option<String>> = vec![];
|
||||
let mut w_path: Vec<Option<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::max(v_post_order_num.1, w_post_order_num.1);
|
||||
|
||||
let mut parent: Option<String> = Some(v.clone());
|
||||
loop {
|
||||
parent = parent.and_then(|p| g.parent(&p).cloned());
|
||||
v_path.push(parent.clone());
|
||||
|
||||
let Some(parent_id) = parent.as_ref() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let Some(post_order_num) = post_order_nums.get(parent_id) else {
|
||||
break;
|
||||
};
|
||||
if post_order_num.0 <= low && lim <= post_order_num.1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lca = parent.clone();
|
||||
|
||||
parent = Some(w.clone());
|
||||
loop {
|
||||
parent = parent.and_then(|p| g.parent(&p).cloned());
|
||||
if parent == lca {
|
||||
break;
|
||||
}
|
||||
|
||||
w_path.push(parent.clone());
|
||||
|
||||
if parent.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
w_path.reverse();
|
||||
v_path.extend(w_path);
|
||||
(v_path, lca)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
734
third_party/dagre_rust/src/layout/position/bk.rs
vendored
Normal file
734
third_party/dagre_rust/src/layout/position/bk.rs
vendored
Normal file
|
|
@ -0,0 +1,734 @@
|
|||
/*
|
||||
* 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()
|
||||
.filter(|layer| !layer.is_empty())
|
||||
.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..south_end {
|
||||
let v = 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.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: i32 = -1;
|
||||
let mut next_north_pos: i32 = -1;
|
||||
let mut south_pos: usize = 0;
|
||||
|
||||
for (south_lookahead, v) in south.iter().enumerate() {
|
||||
if let Some(v_node) = g.node(v) {
|
||||
if v_node.dummy.as_deref() == Some("border") {
|
||||
let predecessors = g.predecessors(v).unwrap_or(vec![]);
|
||||
if !predecessors.is_empty() {
|
||||
next_north_pos =
|
||||
g.node(&predecessors[0]).unwrap().order.unwrap_or(0) as i32;
|
||||
scan(
|
||||
g,
|
||||
south,
|
||||
south_pos,
|
||||
south_lookahead,
|
||||
prev_north_pos,
|
||||
next_north_pos,
|
||||
conflicts,
|
||||
);
|
||||
south_pos = south_lookahead;
|
||||
prev_north_pos = next_north_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan(
|
||||
g,
|
||||
south,
|
||||
south_pos,
|
||||
south.len(),
|
||||
next_north_pos,
|
||||
north.len() as i32,
|
||||
conflicts,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for i in 1..layering.len() {
|
||||
if layering[i - 1].is_empty() || layering[i].is_empty() {
|
||||
continue;
|
||||
}
|
||||
visit_layer(g, &layering[i - 1], &layering[i], &mut conflicts);
|
||||
}
|
||||
|
||||
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)
|
||||
.into_iter()
|
||||
.filter(|w| pos.contains_key(w))
|
||||
.collect();
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let align_keys: Vec<String> = align.keys().cloned().collect();
|
||||
return (root, align_keys);
|
||||
}
|
||||
|
||||
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 pred_x = xs.get(&e.v).cloned().unwrap_or(0.0);
|
||||
let sep = block_g.edge_with_obj(&e).cloned().unwrap_or(0.0);
|
||||
let ev = pred_x + sep;
|
||||
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 succ_x = xs.get(&e.w).cloned().unwrap_or(0.0);
|
||||
let sep = block_g.edge_with_obj(&e).cloned().unwrap_or(0.0);
|
||||
let ev = succ_x - sep;
|
||||
|
||||
acc.min(ev as f64)
|
||||
});
|
||||
|
||||
if min != f64::INFINITY {
|
||||
let Some(node) = g.node(elem) else {
|
||||
return;
|
||||
};
|
||||
if node.border_type.as_ref() != Some(border_type) {
|
||||
xs.insert(
|
||||
elem.clone(),
|
||||
xs.get(elem).cloned().unwrap_or(0.0).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.successors(v).unwrap_or(vec![]) }),
|
||||
&block_g,
|
||||
&mut xs,
|
||||
g,
|
||||
&border_type,
|
||||
);
|
||||
|
||||
// Assign x coordinates to all nodes
|
||||
align.iter().for_each(|v| {
|
||||
let Some(root_id) = root.get(v) else {
|
||||
return;
|
||||
};
|
||||
let Some(root_x) = xs.get(root_id).cloned() else {
|
||||
return;
|
||||
};
|
||||
xs.insert(v.clone(), root_x);
|
||||
});
|
||||
|
||||
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();
|
||||
if u_root != v_root {
|
||||
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);
|
||||
if layering.iter().all(|layer| layer.is_empty()) {
|
||||
return OrderedHashMap::new();
|
||||
}
|
||||
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();
|
||||
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 }
|
||||
}
|
||||
|
||||
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;
|
||||
delta = 0.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 }
|
||||
}
|
||||
|
||||
sum
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn width(g: &Graph<GraphConfig, GraphNode, GraphEdge>, v: &String) -> f32 {
|
||||
g.node(v).unwrap().width
|
||||
}
|
||||
36
third_party/dagre_rust/src/layout/position/mod.rs
vendored
Normal file
36
third_party/dagre_rust/src/layout/position/mod.rs
vendored
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;
|
||||
});
|
||||
}
|
||||
137
third_party/dagre_rust/src/layout/rank/feasible_tree.rs
vendored
Normal file
137
third_party/dagre_rust/src/layout/rank/feasible_tree.rs
vendored
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
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()));
|
||||
|
||||
while tight_tree(&mut t, g) < size {
|
||||
if let Some(edge) = find_min_stack_edge(&t, g) {
|
||||
let delta = if t.has_node(&edge.v) {
|
||||
slack(g, &edge)
|
||||
} else {
|
||||
-1 * slack(g, &edge)
|
||||
};
|
||||
shift_ranks(&t, g, delta);
|
||||
}
|
||||
}
|
||||
|
||||
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_)| 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
third_party/dagre_rust/src/layout/rank/mod.rs
vendored
Normal file
53
third_party/dagre_rust/src/layout/rank/mod.rs
vendored
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);
|
||||
}
|
||||
365
third_party/dagre_rust/src/layout/rank/network_simplex.rs
vendored
Normal file
365
third_party/dagre_rust/src/layout/rank/network_simplex.rs
vendored
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::layout::rank::feasible_tree::feasible_tree;
|
||||
use crate::layout::rank::util::{longest_path, slack};
|
||||
use crate::layout::util;
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::algo::postorder::postorder;
|
||||
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>) {
|
||||
let mut simplified = util::simplify(g);
|
||||
|
||||
longest_path(&mut simplified);
|
||||
|
||||
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = feasible_tree(&mut simplified);
|
||||
init_low_lim_values(&mut t, None);
|
||||
init_cut_values(&mut t, &mut simplified);
|
||||
|
||||
while let Some(e) = leave_edge(&t) {
|
||||
let Some(f) = enter_edge(&t, &simplified, &e) else {
|
||||
break;
|
||||
};
|
||||
exchange_edges(&mut t, &mut simplified, &e, f);
|
||||
}
|
||||
|
||||
for v in g.nodes() {
|
||||
let Some(node) = g.node_mut(&v) else {
|
||||
continue;
|
||||
};
|
||||
let Some(simple_node) = simplified.node(&v) else {
|
||||
continue;
|
||||
};
|
||||
node.rank = simple_node.rank;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 = t.nodes();
|
||||
let mut vs = postorder(t, &node_ids);
|
||||
vs.pop();
|
||||
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 Some(root) = t.nodes().first().cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut children: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for v in t.nodes() {
|
||||
let parent = t.node(&v).unwrap_or(&GraphNode::default()).parent.clone();
|
||||
let Some(parent) = parent else {
|
||||
continue;
|
||||
};
|
||||
|
||||
children.entry(parent).or_default().push(v);
|
||||
}
|
||||
|
||||
let mut stack: Vec<String> = vec![root.clone()];
|
||||
while let Some(parent) = stack.pop() {
|
||||
let Some(vs) = children.get(&parent).cloned() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for v in vs {
|
||||
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 = edge.unwrap_or(&GraphEdge::default()).minlen.unwrap_or(0.0);
|
||||
let parent_rank = g
|
||||
.node(&parent)
|
||||
.unwrap_or(&GraphNode::default())
|
||||
.rank
|
||||
.unwrap_or(0);
|
||||
if let Some(v_node) = g.node_mut(&v) {
|
||||
v_node.rank = Some(
|
||||
parent_rank
|
||||
+ if flipped {
|
||||
minlen as i32
|
||||
} else {
|
||||
-(minlen as i32)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
third_party/dagre_rust/src/layout/rank/util.rs
vendored
Normal file
99
third_party/dagre_rust/src/layout/rank/util.rs
vendored
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;
|
||||
}
|
||||
393
third_party/dagre_rust/src/layout/util.rs
vendored
Normal file
393
third_party/dagre_rust/src/layout/util.rs
vendored
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
use crate::layout::{GraphConfig, GraphEdge, GraphNode};
|
||||
use crate::GraphEdgePoint;
|
||||
use graphlib_rust::{Graph, GraphOption};
|
||||
use ordered_hashmap::OrderedHashMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
// VENDORING PATCH: upstream used a `static mut` mutated in an `unsafe` block,
|
||||
// which is a data race when the engine renders on multiple threads (e.g. the
|
||||
// parallel `cargo test` suite). An `AtomicUsize` is behaviour-preserving (still
|
||||
// hands out monotonic unique ids) and removes the only `unsafe` in this crate.
|
||||
static UNIQUE_STARTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub fn unique_id() -> usize {
|
||||
UNIQUE_STARTER.fetch_add(1, Ordering::Relaxed) + 1
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
}));
|
||||
simplified.set_graph(g.graph().clone());
|
||||
|
||||
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_else(|| {
|
||||
let mut edge = GraphEdge::default();
|
||||
edge.weight = Some(0.0);
|
||||
edge.minlen = Some(1.0);
|
||||
edge
|
||||
});
|
||||
|
||||
let edge_label = edge_label_.cloned().unwrap_or_else(|| {
|
||||
let mut edge = GraphEdge::default();
|
||||
edge.weight = Some(0.0);
|
||||
edge.minlen = Some(1.0);
|
||||
edge
|
||||
});
|
||||
|
||||
let minlen = edge_label.minlen.unwrap_or(1.0);
|
||||
simple_label.minlen = Some(simple_label.minlen.unwrap_or(1.0).max(minlen));
|
||||
|
||||
let weight = edge_label.weight.unwrap_or(0.0);
|
||||
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 {
|
||||
return GraphEdgePoint { x: x + w, y };
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_close(left: f32, right: f32) {
|
||||
assert!((left - right).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_rect_returns_boundary_point_for_center_point() {
|
||||
let rect = Rect {
|
||||
x: 10.0,
|
||||
y: 20.0,
|
||||
width: 8.0,
|
||||
height: 4.0,
|
||||
};
|
||||
let point = GraphEdgePoint { x: 10.0, y: 20.0 };
|
||||
|
||||
let intersection = intersect_rect(&rect, &point);
|
||||
|
||||
assert_close(intersection.x, 14.0);
|
||||
assert_close(intersection.y, 20.0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 Some(rank) = node.rank else {
|
||||
return;
|
||||
};
|
||||
|
||||
let layer: &mut OrderedHashMap<usize, String> = layering.get_mut(rank as usize).unwrap();
|
||||
layer.insert(node.order.unwrap_or(0), v.clone());
|
||||
});
|
||||
|
||||
layering
|
||||
.into_iter()
|
||||
.map(|layer| {
|
||||
let mut keys: Vec<usize> = layer.keys().cloned().collect();
|
||||
keys.sort();
|
||||
keys.iter()
|
||||
.map(|key| 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>) {
|
||||
let nodes: Vec<String> = graph.nodes();
|
||||
if nodes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let node_ranks: Vec<i32> = nodes
|
||||
.iter()
|
||||
.map(|v| {
|
||||
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 max_rank: i32 = node_ranks.iter().max().cloned().unwrap_or(0) - offset;
|
||||
|
||||
let mut layers: Vec<Vec<String>> = vec![Vec::new(); (max_rank + 1).max(0) as usize];
|
||||
for v in &nodes {
|
||||
let rank = graph
|
||||
.node(v)
|
||||
.unwrap_or(&GraphNode::default())
|
||||
.rank
|
||||
.unwrap_or(0)
|
||||
- offset;
|
||||
if rank >= 0 {
|
||||
layers[rank as usize].push(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let node_rank_factor = graph.graph().node_rank_factor.clone().unwrap_or(0.0) as i32;
|
||||
if node_rank_factor <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut delta = 0;
|
||||
for (i, vs) in layers.iter().enumerate() {
|
||||
let i = i as i32;
|
||||
if vs.is_empty() && i % node_rank_factor != 0 {
|
||||
delta -= 1;
|
||||
} else if delta != 0 {
|
||||
for v in vs {
|
||||
if let Some(node) = graph.node_mut(v) {
|
||||
node.rank = Some(node.rank.unwrap_or(0) + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
.filter_map(|v| g.node(v).and_then(|n| n.rank))
|
||||
.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
third_party/dagre_rust/src/lib.rs
vendored
Normal file
131
third_party/dagre_rust/src/lib.rs
vendored
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue