diff --git a/examples/benchmark.rs b/examples/benchmark.rs index a8e325f..b9405fe 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -1,6 +1,7 @@ //! This is a sample program that prints a bunch of elements to an SVG file so //! we can visually see if the things that we render look right. +use layout::adt::dag::SubgraphHandle; use layout::backends::svg::SVGWriter; use layout::core::base::Orientation; use layout::core::color::Color; @@ -10,7 +11,15 @@ pub const LAYOUT_HELPER: bool = true; fn test_main(n_node: usize, _n_edge: usize) { let mut svg = SVGWriter::new(); - let mut gb = VisualGraph::new(Orientation::LeftToRight); + let mut gb = VisualGraph::new( + Orientation::LeftToRight, + Element::create( + ShapeKind::Frame(Some("Main Graph".to_string())), + StyleAttr::new(Color::transparent(), 0, None, 0, 0), + Orientation::LeftToRight, + Point::zero(), + ), + ); for i in 0..n_node { let elem = Element::create( @@ -19,7 +28,7 @@ fn test_main(n_node: usize, _n_edge: usize) { Orientation::LeftToRight, Point::zero(), ); - gb.add_node(elem); + gb.add_node(elem, SubgraphHandle::new(0)); } let t0 = std::time::Instant::now(); diff --git a/inputs/cluster_1.dot b/inputs/cluster_1.dot new file mode 100644 index 0000000..863ea19 --- /dev/null +++ b/inputs/cluster_1.dot @@ -0,0 +1,28 @@ +digraph G { + subgraph cluster_0 { + B -> I -> P + B -> K -> S + A + C + } + + subgraph cluster_1 { + E -> H -> T + M + } + + subgraph cluster_2 { + D + G -> J + N Q + } + A -> L -> U + K -> O + N -> T + D -> M -> Q + F -> N + J -> R + C -> H + + +} \ No newline at end of file diff --git a/inputs/cluster_2.dot b/inputs/cluster_2.dot new file mode 100644 index 0000000..4b3c84f --- /dev/null +++ b/inputs/cluster_2.dot @@ -0,0 +1,32 @@ +digraph G { + subgraph cluster_0 { + B -> I -> P + B -> K -> S + A + C + } + + subgraph cluster_1 { + E -> H -> T + M + } + + subgraph cluster_2 { + D + G -> J + N Q + + subgraph cluster_3 { + Y -> Z + } + } + A -> L -> U + K -> O + N -> T + D -> M -> Q + F -> N + J -> R + C -> H + C -> Z + +} \ No newline at end of file diff --git a/inputs/comment_tab.dot b/inputs/comment_tab.dot new file mode 100644 index 0000000..490f8de --- /dev/null +++ b/inputs/comment_tab.dot @@ -0,0 +1,22 @@ +digraph G { + + subgraph cluster_0 { + style=filled; + color=lightgrey; + node [style=filled,color=white]; + a0 -> a1 -> a2 -> a3; + label = "process #1"; + } + + + start -> a0; + start -> b0; + // a1 -> b3; +// b2 -> a3; + a3 -> a0; + a3 -> end; + b3 -> end; + + start [shape=Mdiamond]; + end [shape=Msquare]; +} diff --git a/inputs/complex_nested.dot b/inputs/complex_nested.dot new file mode 100644 index 0000000..40f7cab --- /dev/null +++ b/inputs/complex_nested.dot @@ -0,0 +1,212 @@ +digraph ComplexSystem { + // Global graph settings + graph [fontname="Helvetica" fontsize=12 compound=true shape=circle]; + node [fontname="Helvetica" fontsize=10 shape=box style=filled]; + edge [fontname="Helvetica" fontsize=9]; + + // Main title + labelloc=t; + fontsize=16; + + // Frontend Layer + subgraph cluster_frontend { + style=filled; + color=lightblue; + fillcolor="#e6f3ff"; + + // Web Application cluster + subgraph cluster_web { + label="Web Application"; + style=filled; + fillcolor="#cce5ff"; + + subgraph cluster_ui { + label="UI Components"; + style=filled; + fillcolor="#b3d9ff"; + + ui_login [label="Login\nComponent" fillcolor="#99ccff"]; + ui_dashboard [label="Dashboard\nComponent" fillcolor="#99ccff"]; + ui_reports [label="Reports\nComponent" fillcolor="#99ccff"]; + } + + subgraph cluster_state { + label="State Management"; + style=filled; + fillcolor="#b3d9ff"; + + state_store [label="Redux Store" fillcolor="#99ccff"]; + state_actions [label="Actions" fillcolor="#99ccff"]; + state_reducers [label="Reducers" fillcolor="#99ccff"]; + } + } + + // Mobile Application cluster + subgraph cluster_mobile { + label="Mobile Application"; + style=filled; + fillcolor="#cce5ff"; + + mobile_ios [label="iOS App" fillcolor="#99ccff"]; + mobile_android [label="Android App" fillcolor="#99ccff"]; + mobile_shared [label="Shared Logic" fillcolor="#99ccff"]; + } + } + + // Backend Layer + subgraph cluster_backend { + label="Backend Layer"; + style=filled; + color=lightcoral; + fillcolor="#ffe6e6"; + + // API Gateway + subgraph cluster_gateway { + label="API Gateway"; + style=filled; + fillcolor="#ffcccc"; + + gw_auth [label="Authentication" fillcolor="#ffb3b3"]; + gw_routing [label="Request Routing" fillcolor="#ffb3b3"]; + gw_ratelimit [label="Rate Limiting" fillcolor="#ffb3b3"]; + } + + gw_auth -> A -> B + + // Microservices + subgraph cluster_services { + label="Microservices"; + style=filled; + fillcolor="#ffcccc"; + + subgraph cluster_user_service { + label="User Service"; + style=filled; + fillcolor="#ffb3b3"; + + user_api [label="User API" fillcolor="#ff9999"]; + user_logic [label="Business Logic" fillcolor="#ff9999"]; + user_validation [label="Validation" fillcolor="#ff9999"]; + } + + subgraph cluster_order_service { + label="Order Service"; + style=filled; + fillcolor="#ffb3b3"; + + order_api [label="Order API" fillcolor="#ff9999"]; + order_logic [label="Business Logic" fillcolor="#ff9999"]; + order_payment [label="Payment Handler" fillcolor="#ff9999"]; + } + + subgraph cluster_inventory_service { + label="Inventory Service"; + style=filled; + fillcolor="#ffb3b3"; + + inv_api [label="Inventory API" fillcolor="#ff9999"]; + inv_logic [label="Stock Management" fillcolor="#ff9999"]; + inv_alerts [label="Alert System" fillcolor="#ff9999"]; + } + } + } + + // Data Layer + subgraph cluster_data { + label="Data Layer"; + style=filled; + color=lightgreen; + fillcolor="#e6ffe6"; + + subgraph cluster_databases { + label="Databases"; + style=filled; + fillcolor="#ccffcc"; + + db_users [label="Users DB\n(PostgreSQL)" shape=cylinder fillcolor="#b3ffb3"]; + db_orders [label="Orders DB\n(PostgreSQL)" shape=cylinder fillcolor="#b3ffb3"]; + db_inventory [label="Inventory DB\n(MongoDB)" shape=cylinder fillcolor="#b3ffb3"]; + } + + subgraph cluster_cache { + label="Cache Layer"; + style=filled; + fillcolor="#ccffcc"; + + cache_redis [label="Redis Cache" shape=cylinder fillcolor="#b3ffb3"]; + cache_sessions [label="Session Store" shape=cylinder fillcolor="#b3ffb3"]; + } + + subgraph cluster_messaging { + label="Message Queue"; + style=filled; + fillcolor="#ccffcc"; + + mq_events [label="Event Bus\n(RabbitMQ)" shape=parallelogram fillcolor="#b3ffb3"]; + mq_jobs [label="Job Queue" shape=parallelogram fillcolor="#b3ffb3"]; + } + } + + // External Services + subgraph cluster_external { + label="External Services"; + style=filled; + color=lightyellow; + fillcolor="#fffacd"; + + ext_payment [label="Payment Gateway" fillcolor="#ffec8b"]; + ext_email [label="Email Service" fillcolor="#ffec8b"]; + ext_analytics [label="Analytics" fillcolor="#ffec8b"]; + } + + // Frontend connections + ui_login -> state_actions; + ui_dashboard -> state_store; + ui_reports -> state_store; + state_actions -> state_reducers; + state_reducers -> state_store; + + mobile_ios -> mobile_shared; + mobile_android -> mobile_shared; + + // Frontend to Gateway connections (using compound for cluster edges) + state_store -> gw_routing [ltail=cluster_web lhead=cluster_gateway label="HTTPS"]; + mobile_shared -> gw_routing [ltail=cluster_mobile lhead=cluster_gateway label="HTTPS"]; + + // Gateway to Services + gw_auth -> user_api [lhead=cluster_user_service]; + gw_routing -> order_api [lhead=cluster_order_service]; + gw_routing -> inv_api [lhead=cluster_inventory_service]; + gw_ratelimit -> gw_routing; + + // Service internal flows + user_api -> user_logic -> user_validation; + order_api -> order_logic -> order_payment; + inv_api -> inv_logic -> inv_alerts; + + // Services to Data Layer + user_logic -> db_users; + order_logic -> db_orders; + inv_logic -> db_inventory; + + user_logic -> cache_redis [label="cache"]; + order_logic -> cache_sessions [label="session"]; + + // Messaging connections + order_logic -> mq_events [label="publish\nevents"]; + inv_logic -> mq_events [label="subscribe"]; + order_payment -> mq_jobs [label="async jobs"]; + + // Service to External + order_payment -> ext_payment [label="process\npayment"]; + user_logic -> ext_email [label="notifications"]; + gw_routing -> ext_analytics [ltail=cluster_gateway label="metrics"]; + + // Cross-service communication + order_logic -> inv_api [label="check stock" style=dashed color=blue]; + inv_logic -> order_api [label="update status" style=dashed color=blue]; + + db_orders -> cache_redis + + +} \ No newline at end of file diff --git a/inputs/complex_nested_2.dot b/inputs/complex_nested_2.dot new file mode 100644 index 0000000..f18824d --- /dev/null +++ b/inputs/complex_nested_2.dot @@ -0,0 +1,343 @@ +digraph ComprehensiveTest { + // Global graph attributes + label="Comprehensive Nested Subgraph Test\nTesting Multiple Layout Engines"; + labelloc=t; + fontsize=20; + fontname="Arial Bold"; + compound=true; + newrank=true; + ranksep=1.5; + nodesep=0.8; + + // Default node and edge styles + node [shape=box, style="rounded,filled", fillcolor=lightblue, fontname="Arial"]; + edge [fontname="Arial", fontsize=10]; + + // Root level nodes + start [label="Start", shape=circle, fillcolor=lightgreen, width=1.5]; + end [label="End", shape=doublecircle, fillcolor=lightcoral, width=1.5]; + + // ========== CLUSTER 1: System Architecture ========== + subgraph cluster_system { + label="System Architecture"; + style=filled; + fillcolor=lightyellow; + penwidth=2; + + // Frontend subcluster + subgraph cluster_frontend { + label="Frontend Layer"; + style="filled,dashed"; + fillcolor=lightcyan; + + ui1 [label="UI Component 1"]; + ui2 [label="UI Component 2"]; + ui3 [label="UI Component 3"]; + + // Nested service layer + subgraph cluster_frontend_services { + label="Frontend Services"; + fillcolor=white; + + fs1 [label="Auth Service"]; + fs2 [label="State Manager"]; + fs3 [label="API Client"]; + + fs1 -> fs2 [label="updates"]; + fs2 -> fs3 [label="requests"]; + } + + ui1 -> fs1; + ui2 -> fs2; + ui3 -> fs3; + } + + // Backend subcluster + subgraph cluster_backend { + label="Backend Layer"; + style="filled,dashed"; + fillcolor=lightpink; + + api1 [label="REST API"]; + api2 [label="GraphQL API"]; + + // Nested business logic + subgraph cluster_business_logic { + label="Business Logic"; + fillcolor=white; + + bl1 [label="User Service"]; + bl2 [label="Order Service"]; + bl3 [label="Payment Service"]; + + // Deeply nested validation layer + subgraph cluster_validation { + label="Validation Layer"; + fillcolor=lavender; + style=dotted; + + v1 [label="Input Validator"]; + v2 [label="Business Rules"]; + v3 [label="Security Check"]; + + v1 -> v2 -> v3; + } + + bl1 -> v1 [lhead=cluster_validation]; + bl2 -> v1 [lhead=cluster_validation]; + bl3 -> v1 [lhead=cluster_validation]; + } + + api1 -> bl1; + api1 -> bl2; + api2 -> bl2; + api2 -> bl3; + } + + // Cross-cluster edges + fs3 -> api1 [ltail=cluster_frontend_services, lhead=cluster_backend, label="HTTP", color=blue, penwidth=2]; + } + + // ========== CLUSTER 2: Data Layer ========== + subgraph cluster_data { + label="Data Layer"; + style=filled; + fillcolor=lightgreen; + penwidth=2; + + subgraph cluster_databases { + label="Databases"; + fillcolor=white; + + db1 [label="PostgreSQL", shape=cylinder]; + db2 [label="MongoDB", shape=cylinder]; + db3 [label="Redis Cache", shape=cylinder, fillcolor=orange]; + } + + subgraph cluster_storage { + label="Storage Systems"; + fillcolor=white; + + s1 [label="Object Storage"]; + s2 [label="File System"]; + + subgraph cluster_backup { + label="Backup Systems"; + fillcolor=lightgray; + + bk1 [label="Primary Backup"]; + bk2 [label="Disaster Recovery"]; + bk3 [label="Archive"]; + + bk1 -> bk2 [label="replicate", style=dashed]; + bk2 -> bk3 [label="archive", style=dashed]; + } + + s1 -> bk1 [lhead=cluster_backup]; + s2 -> bk1 [lhead=cluster_backup]; + } + } + + // ========== CLUSTER 3: External Services ========== + subgraph cluster_external { + label="External Services"; + style=filled; + fillcolor=mistyrose; + penwidth=2; + + subgraph cluster_payment_providers { + label="Payment Providers"; + fillcolor=white; + + pp1 [label="Stripe", shape=component]; + pp2 [label="PayPal", shape=component]; + } + + subgraph cluster_messaging { + label="Messaging Services"; + fillcolor=white; + + msg1 [label="Email Service"]; + msg2 [label="SMS Service"]; + msg3 [label="Push Notifications"]; + + subgraph cluster_templates { + label="Template Engine"; + fillcolor=lavender; + + t1 [label="Email Templates"]; + t2 [label="SMS Templates"]; + + t1 -> msg1 [dir=back]; + t2 -> msg2 [dir=back]; + } + } + + subgraph cluster_analytics { + label="Analytics & Monitoring"; + fillcolor=white; + + an1 [label="Google Analytics"]; + an2 [label="Mixpanel"]; + an3 [label="Sentry"]; + } + } + + // ========== CLUSTER 4: Infrastructure ========== + subgraph cluster_infrastructure { + label="Infrastructure Layer"; + style=filled; + fillcolor=lightsteelblue; + penwidth=2; + + subgraph cluster_compute { + label="Compute Resources"; + fillcolor=white; + + c1 [label="Load Balancer", shape=hexagon]; + + subgraph cluster_app_servers { + label="Application Servers"; + fillcolor=azure; + + as1 [label="App Server 1"]; + as2 [label="App Server 2"]; + as3 [label="App Server 3"]; + + // Internal load distribution + {rank=same; as1; as2; as3} + } + + c1 -> as1; + c1 -> as2; + c1 -> as3; + } + + subgraph cluster_network { + label="Network & Security"; + fillcolor=white; + + fw [label="Firewall", shape=octagon, fillcolor=red, fontcolor=white]; + cdn [label="CDN", shape=hexagon]; + + subgraph cluster_security { + label="Security Services"; + fillcolor=mistyrose; + + waf [label="WAF"]; + ids [label="IDS/IPS"]; + ssl [label="SSL/TLS"]; + + waf -> ids -> ssl; + } + + fw -> waf [lhead=cluster_security]; + } + } + + // ========== CLUSTER 5: DevOps Pipeline ========== + subgraph cluster_devops { + label="CI/CD Pipeline"; + style=filled; + fillcolor=honeydew; + penwidth=2; + + subgraph cluster_source { + label="Source Control"; + fillcolor=white; + + git [label="Git Repository", shape=folder]; + } + + subgraph cluster_ci { + label="Continuous Integration"; + fillcolor=white; + + build [label="Build"]; + test [label="Test"]; + scan [label="Security Scan"]; + + subgraph cluster_tests { + label="Test Suites"; + fillcolor=lightblue; + + ut [label="Unit Tests"]; + it [label="Integration Tests"]; + e2e [label="E2E Tests"]; + + ut -> it -> e2e; + } + + build -> test; + test -> ut [lhead=cluster_tests]; + test -> scan; + } + + subgraph cluster_cd { + label="Continuous Deployment"; + fillcolor=white; + + staging [label="Staging"]; + prod [label="Production"]; + + subgraph cluster_deployment { + label="Deployment Strategies"; + fillcolor=lightgreen; + + blue [label="Blue/Green"]; + canary [label="Canary"]; + rolling [label="Rolling"]; + + {rank=same; blue; canary; rolling} + } + + staging -> prod; + staging -> blue [lhead=cluster_deployment]; + } + + git -> build [lhead=cluster_ci]; + scan -> staging [ltail=cluster_ci, lhead=cluster_cd]; + } + + // ========== Complex Cross-Cluster Connections ========== + + // Start to system + start -> ui1 [lhead=cluster_system, label="user input", color=green, penwidth=2]; + + // System to data + bl1 -> db1 [ltail=cluster_business_logic, lhead=cluster_data, label="CRUD ops", color=purple]; + bl2 -> db2 [ltail=cluster_business_logic, lhead=cluster_databases]; + + // System to external services + bl3 -> pp1 [ltail=cluster_backend, lhead=cluster_payment_providers, label="payment", color=gold, penwidth=2]; + v3 -> an3 [ltail=cluster_validation, lhead=cluster_analytics, label="logs", style=dotted]; + + // Infrastructure connections + c1 -> fs3 [ltail=cluster_infrastructure, lhead=cluster_frontend, label="serve", color=blue]; + fw -> c1 [lhead=cluster_compute, label="traffic"]; + + // DevOps to infrastructure + prod -> as1 [ltail=cluster_cd, lhead=cluster_app_servers, label="deploy", color=green, style=bold]; + + // Monitoring connections + as2 -> an3 [ltail=cluster_app_servers, label="metrics", style=dashed, color=gray]; + + // Storage to backup + db1 -> bk1 [lhead=cluster_backup, label="backup", style=dotted]; + + // End connections + bl3 -> end [label="complete", color=red, penwidth=2]; + + // Bidirectional edges + db3 -> bl1 [dir=both, label="cache", color=orange]; + + // Self-referencing edge + api2 -> api2 [label="retry"]; + + // ========== Rank constraints for better layout ========== + {rank=source; start} + {rank=sink; end} + + // Invisible edges for alignment (commented out by default) + // ui1 -> api1 [style=invis]; +} \ No newline at end of file diff --git a/inputs/dag_diamond.dot b/inputs/dag_diamond.dot new file mode 100644 index 0000000..9044e99 --- /dev/null +++ b/inputs/dag_diamond.dot @@ -0,0 +1,7 @@ +digraph mygraph { + node [shape=box]; +a0 -> a1 +a0 -> a2 +a1 -> a3 +a2 ->a3 +} diff --git a/inputs/multiple_empty_clusters.dot b/inputs/multiple_empty_clusters.dot new file mode 100644 index 0000000..477c52f --- /dev/null +++ b/inputs/multiple_empty_clusters.dot @@ -0,0 +1,7 @@ +digraph test3 { + subgraph cluster_empty0 {} + a -> b; + subgraph cluster_empty1 {} + b -> c; + subgraph cluster_empty2 {} +} diff --git a/inputs/nested_empty_clusters.dot b/inputs/nested_empty_clusters.dot new file mode 100644 index 0000000..53ca372 --- /dev/null +++ b/inputs/nested_empty_clusters.dot @@ -0,0 +1,13 @@ +digraph test6 { + a -> b; + subgraph cluster_level1 { + subgraph cluster_level2 { + subgraph cluster_level3 { + subgraph cluster_level4 { + subgraph cluster_level5 {} + } + } + } + } + b -> c; +} \ No newline at end of file diff --git a/inputs/noncluster_sg.dot b/inputs/noncluster_sg.dot new file mode 100644 index 0000000..48fca2c --- /dev/null +++ b/inputs/noncluster_sg.dot @@ -0,0 +1,30 @@ +digraph G { + subgraph noncluster_0 { +style=filled +node [fontname="Helvetica" fontsize=10 shape=box style=filled]; + B -> I -> P + B -> K -> S + A + C + } + + subgraph noncluster_1 { + E -> H -> T + M + } + + subgraph noncluster_2 { + D + G -> J + N Q + } + A -> L -> U + K -> O + N -> T + D -> M -> Q + F -> N + J -> R + C -> H + + +} \ No newline at end of file diff --git a/inputs/only_empty_subgraphs.dot b/inputs/only_empty_subgraphs.dot new file mode 100644 index 0000000..c2820bc --- /dev/null +++ b/inputs/only_empty_subgraphs.dot @@ -0,0 +1,11 @@ +digraph test10 { + subgraph {} + subgraph cluster_a {} + subgraph { + subgraph { + subgraph cluster_b { + subgraph {} + } + } + } +} \ No newline at end of file diff --git a/inputs/simple_empty_cluster.dot b/inputs/simple_empty_cluster.dot new file mode 100644 index 0000000..3c429e3 --- /dev/null +++ b/inputs/simple_empty_cluster.dot @@ -0,0 +1,5 @@ +digraph test2 { + a -> b; + subgraph cluster_empty {} + b -> c; +} \ No newline at end of file diff --git a/inputs/simple_empty_subgraph.dot b/inputs/simple_empty_subgraph.dot new file mode 100644 index 0000000..8e9739f --- /dev/null +++ b/inputs/simple_empty_subgraph.dot @@ -0,0 +1,5 @@ +digraph test1 { + a -> b; + subgraph {} + b -> c; +} \ No newline at end of file diff --git a/inputs/subgraph_after_node.dot b/inputs/subgraph_after_node.dot new file mode 100644 index 0000000..1cb2749 --- /dev/null +++ b/inputs/subgraph_after_node.dot @@ -0,0 +1,12 @@ +digraph test10 { + a + subgraph {} + subgraph cluster_a {} + subgraph { + subgraph { + subgraph cluster_b { + subgraph {} + } + } + } +} \ No newline at end of file diff --git a/inputs/subgraph_same_level.dot b/inputs/subgraph_same_level.dot new file mode 100644 index 0000000..7462b78 --- /dev/null +++ b/inputs/subgraph_same_level.dot @@ -0,0 +1,30 @@ +digraph G { + + subgraph cluster_0 { + style=filled; + color=lightgrey; + node [style=filled,color=white]; + a0 -> a1 -> a2 -> a3; + label = "process #1"; + } + + subgraph cluster_1 { + node [style=filled]; + b0 -> b1 -> b2 -> b3; + label = "process #2"; + color=blue + } + start -> a0; + start -> b0; + a1 -> b3; + b2 -> a3; + a3 -> a0; + a3 -> end; + b3 -> end; + a3 -> b3; + b2 -> a3; + a3 -> b2 + + start [shape=Mdiamond]; + end [shape=Msquare]; +} diff --git a/inputs/test.sh b/inputs/test.sh index 5c77452..b92454c 100755 --- a/inputs/test.sh +++ b/inputs/test.sh @@ -1,7 +1,7 @@ #!/bin/bash # exit when any command fails -set -e +# set -e ALL= for file in ./inputs/*.dot; do @@ -9,7 +9,7 @@ for file in ./inputs/*.dot; do ALL="$NAME $ALL" cargo run --bin layout $file -o $NAME $1 $2 $3 NAME=/tmp/out_$RANDOM.svg - ALL="$NAME $ALL" - dot -Tsvg $file -o $NAME + # ALL="$NAME $ALL" + # dot -Tsvg $file -o $NAME done -echo $ALL | xargs firefox & +echo $ALL | xargs brave & diff --git a/inputs/tree.dot b/inputs/tree.dot new file mode 100644 index 0000000..7f5273b --- /dev/null +++ b/inputs/tree.dot @@ -0,0 +1,20 @@ + digraph g { + node [shape = record,height=0.1]; + node0[label = " | G| "]; + node1[label = " | E| "]; + node2[label = " | B| "]; + node3[label = " | F| "]; + node4[label = " | R| "]; + node5[label = " | H| "]; + node6[label = " | Y| "]; + node7[label = " | A| "]; + node8[label = " | C| "]; + "node0":f2 -> "node4":f1; + "node0":f0 -> "node1":f1; + "node1":f0 -> "node2":f1; + "node1":f2 -> "node3":f1; + "node2":f2 -> "node8":f1; + "node2":f0 -> "node7":f1; + "node4":f2 -> "node6":f1; + "node4":f0 -> "node5":f1; + } diff --git a/layout/src/adt/dag.rs b/layout/src/adt/dag.rs index ab06c4d..9359429 100644 --- a/layout/src/adt/dag.rs +++ b/layout/src/adt/dag.rs @@ -9,6 +9,9 @@ use std::cmp; /// The Ranked-DAG data structure. #[derive(Debug)] pub struct DAG { + /// Nesting Tree for Subgraph Hierarchy + nesting_tree: NTree, + /// A list of nodes in the dag. nodes: Vec, @@ -20,6 +23,53 @@ pub struct DAG { /// Perform validation checks. validate: bool, + + /// Ranks for top border of subgraphs + top_border_ranks: Vec>>, + + /// Ranks for bottom border of subgraphs + bottom_border_ranks: Vec>>, +} + +#[derive(Debug)] +struct NTree { + subgraphs: Vec, +} + +#[derive(Debug)] +struct Subgraph { + nodes: Vec, + parent_subgraph_idx: SubgraphHandle, + subgraphs: Vec, + left_borders: Vec, + right_borders: Vec, +} + +#[derive(Debug)] +struct Node { + // Points to other edges. + successors: Vec, + predecessors: Vec, + parent_subgraph_idx: SubgraphHandle, + node_type: NodeType, +} + +#[derive(Debug, PartialEq)] +enum NodeType { + Regular, + Connector, + VerticalBorder, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub(crate) enum ContainerHandle { + Node(NodeHandle), + Subgraph(SubgraphHandle), +} + +#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] +pub struct SubgraphHandle { + pub idx: usize, } /// Used by users to keep track of nodes that are saved in the DAG. @@ -28,6 +78,54 @@ pub struct NodeHandle { idx: usize, } +pub type RankType = Vec>; + +impl NTree { + pub fn new() -> Self { + NTree { + subgraphs: vec![Subgraph::new(SubgraphHandle::new(0))], + } + } + + pub(crate) fn new_subgraph( + &mut self, + subgraph: Subgraph, + parent_subgraph_idx: SubgraphHandle, + ) -> SubgraphHandle { + let idx = self.subgraphs.len(); + self.subgraphs.push(subgraph); + if idx == parent_subgraph_idx.get_index() { + return SubgraphHandle { idx }; + } + self.subgraphs[parent_subgraph_idx.get_index()] + .subgraphs + .push(SubgraphHandle::new(idx)); + SubgraphHandle { idx } + } +} + +impl Subgraph { + pub(crate) fn new(parent_subgraph_idx: SubgraphHandle) -> Self { + Subgraph { + nodes: Vec::new(), + parent_subgraph_idx, + subgraphs: Vec::new(), + left_borders: Vec::new(), + right_borders: Vec::new(), + } + } +} + +impl SubgraphHandle { + pub fn new(idx: usize) -> Self { + SubgraphHandle { idx } + } + + pub fn get_index(&self) -> usize { + self.idx + } +} + impl NodeHandle { pub fn new(x: usize) -> Self { NodeHandle { idx: x } @@ -43,22 +141,22 @@ impl From for NodeHandle { } } -#[derive(Debug)] -struct Node { - // Points to other edges. - successors: Vec, - predecessors: Vec, -} - -pub type RankType = Vec>; - impl Node { - pub fn new() -> Self { + pub fn new( + parent_subgraph_idx: SubgraphHandle, + node_type: NodeType, + ) -> Self { Node { successors: Vec::new(), predecessors: Vec::new(), + parent_subgraph_idx, + node_type, } } + + pub fn get_parent_subgraph_index(&self) -> SubgraphHandle { + self.parent_subgraph_idx + } } /// Node iterator for iterating over nodes in the graph. @@ -82,12 +180,20 @@ impl Iterator for NodeIterator { } } +enum VerticalBorder { + Left, + Right, +} + impl DAG { pub fn new() -> Self { DAG { + nesting_tree: NTree::new(), nodes: Vec::new(), ranks: Vec::new(), levels: Vec::new(), + top_border_ranks: Vec::new(), + bottom_border_ranks: Vec::new(), validate: true, } } @@ -138,11 +244,68 @@ impl DAG { removed_pred } + /// Create a new subgraph. + pub(crate) fn new_subgraph( + &mut self, + parent_subgraph_idx: SubgraphHandle, + ) -> SubgraphHandle { + let subgraph = Subgraph::new(parent_subgraph_idx); + let subgraph_idx = + SubgraphHandle::new(self.nesting_tree.subgraphs.len()); + self.nesting_tree + .new_subgraph(subgraph, parent_subgraph_idx); + subgraph_idx + } + + pub(crate) fn subgraph_left_borders( + &self, + sg: SubgraphHandle, + ) -> &Vec { + &self.nesting_tree.subgraphs[sg.idx].left_borders + } + + pub(crate) fn subgraph_right_borders( + &self, + sg: SubgraphHandle, + ) -> &Vec { + &self.nesting_tree.subgraphs[sg.idx].right_borders + } + /// Create a new node. - pub fn new_node(&mut self) -> NodeHandle { - self.nodes.push(Node::new()); + pub fn new_node( + &mut self, + parent_subgraph_idx: SubgraphHandle, + ) -> NodeHandle { + self.nodes + .push(Node::new(parent_subgraph_idx, NodeType::Regular)); self.levels.push(0); let node = NodeHandle::new(self.nodes.len() - 1); + self.nesting_tree.subgraphs[parent_subgraph_idx.idx] + .nodes + .push(node); + self.add_element_to_rank(node, 0, false); + node + } + + pub(crate) fn new_connector_node( + &mut self, + from: NodeHandle, + to: NodeHandle, + ) -> NodeHandle { + let from_c = self.nesting_edge_pair(from, to).0; + let connector_sg_idx = match from_c { + ContainerHandle::Node(n) => self.get_parent_subgraph_index_n(n), + ContainerHandle::Subgraph(sg) => { + self.get_parent_subgraph_index_sg(sg) + } + }; + self.nodes + .push(Node::new(connector_sg_idx, NodeType::Connector)); + self.levels.push(0); + let node = NodeHandle::new(self.nodes.len() - 1); + self.nesting_tree.subgraphs[connector_sg_idx.idx] + .nodes + .push(node); self.add_element_to_rank(node, 0, false); node } @@ -150,7 +313,8 @@ impl DAG { /// Create \p n new nodes. pub fn new_nodes(&mut self, n: usize) { for _ in 0..n { - self.nodes.push(Node::new()); + self.nodes + .push(Node::new(SubgraphHandle::new(0), NodeType::Regular)); self.levels.push(0); let node = NodeHandle::new(self.nodes.len() - 1); self.add_element_to_rank(node, 0, false); @@ -166,6 +330,27 @@ impl DAG { &self.nodes[from.idx].predecessors } + pub fn get_parent_subgraph_index_n( + &self, + from: NodeHandle, + ) -> SubgraphHandle { + self.nodes[from.idx].get_parent_subgraph_index() + } + + pub fn get_parent_subgraph_index_sg( + &self, + from: SubgraphHandle, + ) -> SubgraphHandle { + self.nesting_tree.subgraphs[from.idx].parent_subgraph_idx + } + + pub fn get_children_subgraphs( + &self, + from: SubgraphHandle, + ) -> &Vec { + &self.nesting_tree.subgraphs[from.idx].subgraphs + } + pub fn single_pred(&self, from: NodeHandle) -> Option { if self.nodes[from.idx].predecessors.len() == 1 { return Some(self.nodes[from.idx].predecessors[0]); @@ -180,6 +365,13 @@ impl DAG { None } + pub fn is_vertical_border(&self, node: NodeHandle) -> bool { + match self.nodes[node.idx].node_type { + NodeType::VerticalBorder => true, + _ => false, + } + } + pub fn verify(&self) { if self.validate { // Check that the node indices are valid. @@ -212,6 +404,103 @@ impl DAG { self.nodes.is_empty() } + pub(crate) fn get_subgraph_levels(&self) -> Vec<(usize, usize)> { + let mut levels = vec![(usize::MAX, usize::MIN); self.num_subgraphs()]; + for (i, node) in self.nodes.iter().enumerate() { + let subgraph_idx = node.get_parent_subgraph_index(); + let level = self.level(NodeHandle::from(i)); + levels[subgraph_idx.idx].0 = + cmp::min(levels[subgraph_idx.idx].0, level); + levels[subgraph_idx.idx].1 = + cmp::max(levels[subgraph_idx.idx].1, level); + } + + for (i, s) in self.nesting_tree.subgraphs.iter().enumerate().rev() { + for child in s.subgraphs.iter() { + levels[i].0 = cmp::min(levels[i].0, levels[child.idx].0); + levels[i].1 = cmp::max(levels[i].1, levels[child.idx].1); + } + } + for (i, s) in self.nesting_tree.subgraphs.iter().enumerate() { + let parent_idx = s.parent_subgraph_idx; + if levels[i].0 > levels[i].1 { + // No nodes in this subgraph. + levels[i].0 = levels[parent_idx.idx].0; + levels[i].1 = levels[parent_idx.idx].0; + } + } + levels + } + + fn is_reachable_inner_c( + &self, + from: ContainerHandle, + to: ContainerHandle, + visited_nodes: &mut Vec, + visited_subgraphs: &mut Vec, + node_successors_c: &Vec>, + subgraph_successors_c: &Vec>, + ) -> bool { + if from == to { + return true; + } + + match from { + ContainerHandle::Node(n) => { + // Don't step into a cycle. + if visited_nodes[n.idx] { + return false; + } + + // Push to the dfs stack. + visited_nodes[n.idx] = true; + + for edge in &node_successors_c[n.idx] { + if self.is_reachable_inner_c( + edge.clone(), + to.clone(), + visited_nodes, + visited_subgraphs, + node_successors_c, + subgraph_successors_c, + ) { + return true; + } + } + + // Pop from the dfs stack. + visited_nodes[n.idx] = false; + } + ContainerHandle::Subgraph(s) => { + // Don't step into a cycle. + if visited_subgraphs[s.idx] { + return false; + } + + // Push to the dfs stack. + visited_subgraphs[s.idx] = true; + + for edge in &subgraph_successors_c[s.idx] { + if self.is_reachable_inner_c( + edge.clone(), + to.clone(), + visited_nodes, + visited_subgraphs, + node_successors_c, + subgraph_successors_c, + ) { + return true; + } + } + + // Pop from the dfs stack. + visited_subgraphs[s.idx] = false; + } + } + + false + } + /// \returns True if the node \to is reachable from the node \p from. /// This internal method is used for the verification of the graph. fn is_reachable_inner( @@ -244,6 +533,30 @@ impl DAG { false } + fn is_reachable_c( + &self, + from: ContainerHandle, + to: ContainerHandle, + node_successors_c: &Vec>, + subgraph_successors_c: &Vec>, + ) -> bool { + if from == to { + return true; + } + let mut visited_nodes = vec![false; self.nodes.len()]; + let mut visited_subgraphs = + vec![false; self.nesting_tree.subgraphs.len()]; + + self.is_reachable_inner_c( + from, + to, + &mut visited_nodes, + &mut visited_subgraphs, + node_successors_c, + subgraph_successors_c, + ) + } + /// \returns True if there is a path from \p 'from' to \p 'to'. pub fn is_reachable(&self, from: NodeHandle, to: NodeHandle) -> bool { if from == to { @@ -255,26 +568,35 @@ impl DAG { self.is_reachable_inner(from, to, &mut visited) } - /// Return the topological sort order of the nodes in the dag. - /// This is implemented as the reverse post order scan. - fn topological_sort(&self) -> Vec { + fn topological_sort_container( + &self, + subgraph_idx: SubgraphHandle, + node_successors_c: &Vec>, + subgraph_successors_c: &Vec>, + ) -> Vec { // A list of vectors in post-order. - let mut order: Vec = Vec::new(); + let mut order: Vec = Vec::new(); // Marks that a node is in the worklist. - let mut visited = Vec::new(); - visited.resize(self.nodes.len(), false); + let mut visited_nodes = vec![false; self.nodes.len()]; + let mut visited_subgraphs = + vec![false; self.nesting_tree.subgraphs.len()]; // A tuple of handle, and command: // true- force push. // false- this is a child to visit. - let mut worklist: Vec<(NodeHandle, bool)> = Vec::new(); + let mut worklist: Vec<(ContainerHandle, bool)> = Vec::new(); // Add all of the values that we want to compute into the worklist. - for n in self.iter() { - worklist.push((n, false)); + for n in self.nesting_tree.subgraphs[subgraph_idx.idx].nodes.iter() { + worklist.push((ContainerHandle::Node(*n), false)); + } + for s in self.nesting_tree.subgraphs[subgraph_idx.idx] + .subgraphs + .iter() + { + worklist.push((ContainerHandle::Subgraph(*s), false)); } - while let Some((current, cmd)) = worklist.pop() { // Handle 'push' commands. if cmd { @@ -282,28 +604,137 @@ impl DAG { continue; } - // Don't visit visited nodes. - if visited[current.idx] { - continue; - } + match current { + ContainerHandle::Node(n) => { + // Don't visit visited nodes. + if visited_nodes[n.idx] { + continue; + } + + visited_nodes[n.idx] = true; - visited[current.idx] = true; + // Save this node after all of the children are handles. + worklist.push((current, true)); - // Save this node after all of the children are handles. - worklist.push((current, true)); + // Add the children to the worklist. + let succs = node_successors_c[n.idx].clone(); - // Add the children to the worklist. - let node = &self.nodes[current.idx]; - for edge in &node.successors { - worklist.push((*edge, false)); + for edge in succs.iter() { + worklist.push((edge.clone(), false)); + } + } + ContainerHandle::Subgraph(s) => { + if visited_subgraphs[s.idx] { + continue; + } + visited_subgraphs[s.idx] = true; + worklist.push((current, true)); + let succs = subgraph_successors_c[s.idx].clone(); + for edge in succs.iter() { + worklist.push((edge.clone(), false)); + } + } } } - // Turn the post-order to a reverse post order. order.reverse(); order } + fn compute_successors_c( + &self, + ) -> ( + Vec>, + Vec>, + Vec>, + Vec>, + ) { + let mut node_successors_c = vec![Vec::new(); self.nodes.len()]; + let mut subgraph_successors_c = + vec![Vec::new(); self.nesting_tree.subgraphs.len()]; + let mut node_successors_pairs = vec![Vec::new(); self.nodes.len()]; + let mut subgraph_successors_pairs = + vec![Vec::new(); self.nesting_tree.subgraphs.len()]; + + for (i, node) in self.nodes.iter().enumerate() { + for succ in node.successors.iter() { + let (from_c, to_c) = + self.nesting_edge_pair(NodeHandle::new(i), *succ); + + if self.is_reachable_c( + to_c.clone(), + from_c.clone(), + &node_successors_c, + &subgraph_successors_c, + ) { + continue; + } + match from_c { + ContainerHandle::Node(n) => { + node_successors_c[n.idx].push(to_c); + node_successors_pairs[n.idx] + .push((NodeHandle::new(i), *succ)); + } + ContainerHandle::Subgraph(s) => { + subgraph_successors_c[s.idx].push(to_c); + subgraph_successors_pairs[s.idx] + .push((NodeHandle::new(i), *succ)); + } + } + } + } + ( + node_successors_c, + subgraph_successors_c, + node_successors_pairs, + subgraph_successors_pairs, + ) + } + + pub fn compute_level_container(&self) -> Vec { + let mut levels: Vec = Vec::new(); + + // Levels has the same layout as the DAG node list. + levels.resize(self.nodes.len(), 0); + + let (node_successors_c, subgraph_successors_c, node_s_pair, sg_s_pair) = + self.compute_successors_c(); + let subgraphs_num = self.num_subgraphs(); + for s_idx in (0..subgraphs_num).rev() { + let order_c = self.topological_sort_container( + SubgraphHandle::new(s_idx), + &node_successors_c, + &subgraph_successors_c, + ); + for t in order_c { + let (edge_pairs, successors_c) = match t { + ContainerHandle::Node(from) => { + (&node_s_pair[from.idx], &node_successors_c[from.idx]) + } + ContainerHandle::Subgraph(from_s) => ( + &sg_s_pair[from_s.idx], + &subgraph_successors_c[from_s.idx], + ), + }; + for (j, to_t) in successors_c.iter().enumerate() { + let (from, to) = edge_pairs[j]; + self.level_offset_container(from, to, *to_t, &mut levels); + } + } + } + // assign connectors to be average of their end nodes + for (i, node) in self.nodes.iter().enumerate() { + if node.node_type == NodeType::Connector { + let pred = self.single_pred(NodeHandle::from(i)).unwrap(); + let succ = self.single_succ(NodeHandle::from(i)).unwrap(); + // round to nearest integer + let level = (levels[pred.idx] + levels[succ.idx]) / 2; + levels[i] = level; + } + } + levels + } + // The methods below are related to the rank (placing nodes in levels). // /// \returns the number of ranks in the dag. @@ -311,6 +742,10 @@ impl DAG { self.ranks.len() } + pub fn num_subgraphs(&self) -> usize { + self.nesting_tree.subgraphs.len() + } + /// \return a mutable reference to a row at level \p level. pub fn row_mut(&mut self, level: usize) -> &mut Vec { assert!(level < self.ranks.len(), "Invalid rank"); @@ -333,6 +768,16 @@ impl DAG { &mut self.ranks } + /// \returns a reference to the whole border ranks data structure. + pub(crate) fn top_border_ranks(&self) -> &Vec>> { + &self.top_border_ranks + } + + /// \returns a reference to the whole border ranks data structure. + pub(crate) fn bottom_border_ranks(&self) -> &Vec>> { + &self.bottom_border_ranks + } + /// \returns True if \p elem is the first node in the row \p level. pub fn is_first_in_row(&self, elem: NodeHandle, level: usize) -> bool { if level >= self.ranks.len() || self.ranks[level].is_empty() { @@ -375,8 +820,7 @@ impl DAG { /// Places all of the nodes in ranks (levels). pub fn recompute_node_ranks(&mut self) { assert!(!self.is_empty(), "Sorting an empty graph"); - let order = self.topological_sort(); - let levels = self.compute_levels(&order); + let levels = self.compute_level_container(); self.ranks.clear(); for (i, level) in levels.iter().enumerate() { self.add_element_to_rank(NodeHandle::from(i), *level, false); @@ -437,36 +881,592 @@ impl DAG { self.levels[node.get_index()] } - /// Computes and returns the level of each node in the graph based - /// on the traversal order \p order. - fn compute_levels(&self, order: &[NodeHandle]) -> Vec { - let mut levels: Vec = Vec::new(); - assert_eq!(order.len(), self.nodes.len()); + fn level_offset_container( + &self, + from: NodeHandle, + to: NodeHandle, + to_t: ContainerHandle, + levels: &mut Vec, + ) { + match to_t { + ContainerHandle::Node(n) => { + levels[n.idx] = cmp::max(levels[n.idx], levels[from.idx] + 1); + } + ContainerHandle::Subgraph(sg) => { + if levels[from.idx] + 1 <= levels[to.idx] { + return; + } + let offset = (levels[from.idx] + 1) - levels[to.idx]; + for n in self.nesting_tree.subgraphs[sg.idx].nodes.iter() { + levels[n.idx] += offset; + } + let mut worklist = Vec::new(); + for s in self.nesting_tree.subgraphs[sg.idx].subgraphs.iter() { + worklist.push(*s); + } + while let Some(current) = worklist.pop() { + for n in + self.nesting_tree.subgraphs[current.idx].nodes.iter() + { + levels[n.idx] += offset; + } + for s in self.nesting_tree.subgraphs[current.idx] + .subgraphs + .iter() + { + worklist.push(*s); + } + } + } + } + } - // Levels has the same layout as the DAG node list. - levels.resize(self.nodes.len(), 0); + pub(crate) fn place_horizontal_borders( + &mut self, + subgraph_levels: &Vec<(usize, usize)>, + ) { + let num_levels = self.num_levels(); + self.top_border_ranks = vec![vec![]; num_levels]; + self.bottom_border_ranks = vec![vec![]; num_levels]; + + self.top_border_ranks[0] = vec![vec![SubgraphHandle::new(0)]]; + self.bottom_border_ranks[num_levels - 1] = + vec![vec![SubgraphHandle::new(0)]]; + + let mut inner_levels = vec![None; self.num_subgraphs()]; + + inner_levels[0] = Some((0, 0)); + for s in 0..self.num_subgraphs() { + self.new_horizontal_border( + SubgraphHandle::new(s), + subgraph_levels, + &mut inner_levels, + ); + } + } + + fn new_horizontal_border( + &mut self, + subgraph_idx: SubgraphHandle, + levels_map: &Vec<(usize, usize)>, + inner_levels: &mut Vec>, + ) { + let (p_lvl_s, p_lvl_e) = levels_map[subgraph_idx.idx]; + + let (p_in_lvl_s, p_in_lvl_e) = inner_levels[subgraph_idx.idx].expect( + "Ordering subgraph horizontal borders is wrong, this is a bug.", + ); + + let mut c_in_lvl_s; + let mut c_in_lvl_e; - // For each node in the order (starting with a node of level zero). - for src in order { - // Update the level of all successors. - for dest in self.nodes[src.idx].successors.iter() { - // Ignore self edges. - if src.idx == dest.idx { + for child_s in self.nesting_tree.subgraphs[subgraph_idx.idx] + .subgraphs + .iter() + { + let (lvl_s, lvl_e) = levels_map[child_s.idx]; + if lvl_s == p_lvl_s { + if p_in_lvl_s + 1 < self.top_border_ranks[lvl_s].len() { + self.top_border_ranks[lvl_s][p_in_lvl_s + 1].push(*child_s); + } else { + self.top_border_ranks[lvl_s].push(vec![*child_s]); + } + + c_in_lvl_s = p_in_lvl_s + 1; + } else { + if self.top_border_ranks[lvl_s].is_empty() { + self.top_border_ranks[lvl_s].push(vec![*child_s]); + c_in_lvl_s = 0; + } else { + self.top_border_ranks[lvl_s] + .last_mut() + .unwrap() + .push(*child_s); + c_in_lvl_s = self.top_border_ranks[lvl_s].len() - 1; + } + } + + if lvl_e == p_lvl_e { + if p_in_lvl_e + 1 < self.top_border_ranks[lvl_e].len() { + self.top_border_ranks[lvl_e][p_in_lvl_e + 1].push(*child_s); + } else { + self.bottom_border_ranks[lvl_e].push(vec![*child_s]); + } + c_in_lvl_e = p_in_lvl_e + 1; + } else { + if self.bottom_border_ranks[lvl_e].is_empty() { + self.bottom_border_ranks[lvl_e].push(vec![*child_s]); + c_in_lvl_e = 0; + } else { + self.bottom_border_ranks[lvl_e] + .last_mut() + .unwrap() + .push(*child_s); + c_in_lvl_e = self.bottom_border_ranks[lvl_e].len() - 1; + } + } + + inner_levels[child_s.idx] = Some((c_in_lvl_s, c_in_lvl_e)); + } + } + + fn get_tree_path( + &self, + start_sg: SubgraphHandle, + end_sg: SubgraphHandle, + ) -> (Vec, SubgraphHandle, Vec) { + let from_path = self.subgraph_path(start_sg); + let to_path = self.subgraph_path(end_sg); + let max_len = from_path.len().min(to_path.len()); + let mut i = 0; + while i < max_len { + if from_path[i] == to_path[i] { + i += 1; + } else { + break; + } + } + let from_path = from_path.iter().skip(i).rev().cloned().collect(); + (from_path, to_path[i - 1], to_path[i..].to_vec()) + } + + fn new_vertical_border_node( + &mut self, + sg: SubgraphHandle, + level: usize, + border_type: VerticalBorder, + ) -> NodeHandle { + self.nodes.push(Node::new(sg, NodeType::VerticalBorder)); + let node_handle = NodeHandle::new(self.nodes.len() - 1); + self.nesting_tree.subgraphs[sg.idx].nodes.push(node_handle); + self.levels.push(level); + self.ranks[level].push(node_handle); + match border_type { + VerticalBorder::Left => self.nesting_tree.subgraphs[sg.idx] + .left_borders + .push(node_handle), + VerticalBorder::Right => self.nesting_tree.subgraphs[sg.idx] + .right_borders + .push(node_handle), + } + node_handle + } + + pub(crate) fn place_vertical_borders(&mut self) { + let max_level = self.num_levels(); + for ri in 0..max_level { + let row_len = self.ranks[ri].len(); + let old_row = self.ranks[ri].clone(); + self.ranks[ri].clear(); + let sg_path = self + .subgraph_path(self.get_parent_subgraph_index_n(old_row[0])); + for s in sg_path.iter() { + self.new_vertical_border_node(*s, ri, VerticalBorder::Left); + } + for i in 0..(row_len - 1) { + self.ranks[ri].push(old_row[i]); + let curr_sg = self.get_parent_subgraph_index_n(old_row[i]); + let next_sg = self.get_parent_subgraph_index_n(old_row[i + 1]); + if curr_sg == next_sg { continue; } - levels[dest.idx] = - cmp::max(levels[dest.idx], levels[src.idx] + 1); + let (from_path, _, to_path) = + self.get_tree_path(curr_sg, next_sg); + for s in from_path.iter() { + self.new_vertical_border_node( + *s, + ri, + VerticalBorder::Right, + ); + } + + for s in to_path.iter() { + self.new_vertical_border_node(*s, ri, VerticalBorder::Left); + } + } + self.ranks[ri].push(old_row[row_len - 1]); + let sg_path = self.subgraph_path( + self.get_parent_subgraph_index_n(old_row[row_len - 1]), + ); + for s in sg_path.iter().rev() { + self.new_vertical_border_node(*s, ri, VerticalBorder::Right); + } + + // go trhough left and right borders and connect them + for s in 0..self.num_subgraphs() { + let border_len = + self.nesting_tree.subgraphs[s].right_borders.len(); + for i in 1..border_len { + self.add_edge( + self.nesting_tree.subgraphs[s].left_borders[i - 1], + self.nesting_tree.subgraphs[s].left_borders[i], + ); + self.add_edge( + self.nesting_tree.subgraphs[s].right_borders[i - 1], + self.nesting_tree.subgraphs[s].right_borders[i], + ); + } + } + } + // assert right and left border length are equal for each subgraph + for s in 0..self.num_subgraphs() { + assert_eq!( + self.nesting_tree.subgraphs[s].left_borders.len(), + self.nesting_tree.subgraphs[s].right_borders.len(), + "Subgraph {:?} has unbalanced borders", + SubgraphHandle::new(s) + ); + } + } + + fn barycenter_weight_pred( + &self, + node: NodeHandle, + col_map: &Vec, + ) -> f64 { + let mut total_weight = 0; + let total_count = self.predecessors(node).len(); + for p in self.predecessors(node).iter() { + total_weight += col_map[p.get_index()]; + } + + if total_count == 0 { + return col_map[node.get_index()] as f64; + } + total_weight as f64 / total_count as f64 + } + + fn barycenter_weight_succ( + &self, + node: NodeHandle, + col_map: &Vec, + ) -> f64 { + let mut total_weight = 0; + let total_count = self.successors(node).len(); + for p in self.successors(node).iter() { + total_weight += col_map[p.get_index()]; + } + + if total_count == 0 { + return col_map[node.get_index()] as f64; + } + total_weight as f64 / total_count as f64 + } + + fn find_endpoints_of_connectors( + &self, + node: NodeHandle, + ) -> (NodeHandle, NodeHandle) { + let mut pred = node; + let mut succ = node; + assert!( + self.nodes[node.idx].node_type == NodeType::Connector, + "Node {:?} is not a connector node", + node + ); + + while self.nodes[pred.idx].node_type == NodeType::Connector { + let p = self + .single_pred(pred) + .expect("Connector node has no predecessor, this is a bug."); + pred = p; + } + + while self.nodes[succ.idx].node_type == NodeType::Connector { + let s = self + .single_succ(succ) + .expect("Connector node has no successor, this is a bug."); + succ = s; + } + + (pred, succ) + } + + fn aggregate_position(&self) -> (Vec, Vec) { + let subgraphs_num = self.num_subgraphs(); + let mut total_position = vec![(0, 0); subgraphs_num]; + let mut position_nodes = vec![0.0; self.len()]; + + for row in self.ranks.iter() { + for (j, node) in row.iter().enumerate() { + let subgraph_idx = + self.nodes[node.idx].get_parent_subgraph_index(); + total_position[subgraph_idx.idx].0 += j; + total_position[subgraph_idx.idx].1 += 1; + position_nodes[node.idx] = j as f64; + } + } + + for i in (0..subgraphs_num).rev() { + for j in self.nesting_tree.subgraphs[i].subgraphs.iter() { + total_position[i].0 += total_position[j.idx].0; + total_position[i].1 += total_position[j.idx].1; + } + } + + let average_position: Vec = total_position + .into_iter() + .map(|(total, count)| { + if count == 0 { + 0.0 + } else { + total as f64 / count as f64 + } + }) + .collect(); + + for (i, node) in self.nodes.iter().enumerate() { + if node.node_type == NodeType::Connector { + let (pred_n, succ_n) = + self.find_endpoints_of_connectors(NodeHandle::new(i)); + let (pred, succ) = self.nesting_edge_pair(pred_n, succ_n); + let p_pos = match pred { + ContainerHandle::Node(n) => position_nodes[n.idx], + ContainerHandle::Subgraph(s) => average_position[s.idx], + }; + let s_pos = match succ { + ContainerHandle::Node(n) => position_nodes[n.idx], + ContainerHandle::Subgraph(s) => average_position[s.idx], + }; + position_nodes[i] = (p_pos + s_pos) / 2.0; } } - // For each node in the order. - for src in order { - for dest in self.nodes[src.idx].successors.iter() { - assert!(levels[dest.idx] >= levels[src.idx]); + (average_position, position_nodes) + } + + fn aggregate_position_layerwise( + &self, + layer_idx: usize, + ) -> (Vec>, Vec>) { + let subgraphs_num = self.num_subgraphs(); + + let mut total_position = vec![(0, 0); subgraphs_num]; + let mut position_nodes = vec![None; self.len()]; + for (j, node) in self.ranks[layer_idx].iter().enumerate() { + let subgraph_idx = self.nodes[node.idx].get_parent_subgraph_index(); + total_position[subgraph_idx.idx].0 += j; + total_position[subgraph_idx.idx].1 += 1; + position_nodes[node.idx] = Some(j as f64); + } + + for i in 0..subgraphs_num { + for j in self.nesting_tree.subgraphs[subgraphs_num - i - 1] + .subgraphs + .iter() + { + total_position[subgraphs_num - i - 1].0 += + total_position[j.idx].0; + total_position[subgraphs_num - i - 1].1 += + total_position[j.idx].1; } } - levels + let average_position: Vec> = total_position + .into_iter() + .map(|(total, count)| { + if count == 0 { + None + } else { + Some(total as f64 / count as f64) + } + }) + .collect(); + + (average_position, position_nodes) + } + + pub(crate) fn subgraph_order_by_p_layerwise(&mut self, layer_idx: usize) { + let (subgraph_pos_map, node_pos) = + self.aggregate_position_layerwise(layer_idx); + + let mut working_list = + vec![ContainerHandle::Subgraph(SubgraphHandle::new(0))]; + let mut output = vec![]; + + while !working_list.is_empty() { + let current = working_list.pop().unwrap(); + match current { + ContainerHandle::Node(n) => { + output.push(n); + } + ContainerHandle::Subgraph(s) => { + let mut cur = vec![]; + for s_child in + self.nesting_tree.subgraphs[s.idx].subgraphs.iter() + { + if let Some(_) = subgraph_pos_map[s_child.idx] { + cur.push(ContainerHandle::Subgraph(*s_child)); + } + } + + for n_child in + self.nesting_tree.subgraphs[s.idx].nodes.iter() + { + if let Some(_) = node_pos[n_child.idx] { + cur.push(ContainerHandle::Node(*n_child)); + } + } + + cur.sort_by(|a, b| { + let p_a = match a { + ContainerHandle::Node(n) => { + node_pos[n.idx].unwrap() + } + ContainerHandle::Subgraph(sg) => { + subgraph_pos_map[sg.idx].unwrap() + } + }; + let p_b = match b { + ContainerHandle::Node(n) => { + node_pos[n.idx].unwrap() + } + ContainerHandle::Subgraph(sg) => { + subgraph_pos_map[sg.idx].unwrap() + } + }; + p_b.partial_cmp(&p_a).unwrap() + }); + + for c in cur.into_iter() { + working_list.push(c); + } + } + } + } + + self.ranks[layer_idx] = output; + } + + pub(crate) fn subgraph_order_by_p(&mut self) -> Vec { + let (subgraph_pos_map, node_pos) = self.aggregate_position(); + let mut working_list = + vec![ContainerHandle::Subgraph(SubgraphHandle::new(0))]; + let mut output = vec![]; + + while !working_list.is_empty() { + let current = working_list.pop().unwrap(); + match current { + ContainerHandle::Node(n) => { + output.push(n); + } + ContainerHandle::Subgraph(s) => { + let mut cur = vec![]; + for s_child in + self.nesting_tree.subgraphs[s.idx].subgraphs.iter() + { + cur.push(ContainerHandle::Subgraph(*s_child)); + } + for n_child in + self.nesting_tree.subgraphs[s.idx].nodes.iter() + { + cur.push(ContainerHandle::Node(*n_child)); + } + cur.sort_by(|a, b| { + let p_a = match a { + ContainerHandle::Node(n) => node_pos[n.idx], + ContainerHandle::Subgraph(sg) => { + subgraph_pos_map[sg.idx] + } + }; + let p_b = match b { + ContainerHandle::Node(n) => node_pos[n.idx], + ContainerHandle::Subgraph(sg) => { + subgraph_pos_map[sg.idx] + } + }; + p_b.partial_cmp(&p_a).unwrap() + }); + + for c in cur.into_iter() { + working_list.push(c); + } + } + } + } + output + } + + pub(crate) fn node_order_ws_layerwise( + &mut self, + layer_idx: usize, + col_map: &Vec, + ) { + // using barycenter weights to order nodes in the layer + let mut result = Vec::new(); + let mut node_weights = Vec::new(); + for node in self.row(layer_idx).iter() { + let weight = self.barycenter_weight_succ(*node, col_map); + node_weights.push((weight, *node)); + } + // sort by weight + node_weights.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + for (_, node) in node_weights.iter() { + result.push(*node); + } + self.ranks[layer_idx] = result; + } + + pub(crate) fn node_order_wp_layerwise( + &mut self, + layer_idx: usize, + col_map: &Vec, + ) { + // using barycenter weights to order nodes in the layer + let mut result = Vec::new(); + let mut node_weights = Vec::new(); + for node in self.row(layer_idx).iter() { + let weight = self.barycenter_weight_pred(*node, col_map); + node_weights.push((weight, *node)); + } + // sort by weight + node_weights.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + for (_, node) in node_weights.iter() { + result.push(*node); + } + self.ranks[layer_idx] = result; + } + + fn subgraph_path(&self, sg: SubgraphHandle) -> Vec { + let mut path = vec![sg]; + let mut current = sg; + while current.idx != 0 { + current = + self.nesting_tree.subgraphs[current.idx].parent_subgraph_idx; + path.push(current); + } + path.reverse(); + path + } + + fn nesting_edge_pair( + &self, + from: NodeHandle, + to: NodeHandle, + ) -> (ContainerHandle, ContainerHandle) { + let from_path = + self.subgraph_path(self.get_parent_subgraph_index_n(from)); + let to_path = self.subgraph_path(self.get_parent_subgraph_index_n(to)); + let max_len = from_path.len().min(to_path.len()); + let mut i = 0; + while i < max_len { + if from_path[i] != to_path[i] { + break; + } + i += 1; + } + let from_t = if i == from_path.len() { + ContainerHandle::Node(from) + } else { + ContainerHandle::Subgraph(from_path[i]) + }; + + let to_t = if i == to_path.len() { + ContainerHandle::Node(to) + } else { + ContainerHandle::Subgraph(to_path[i]) + }; + (from_t, to_t) } } @@ -479,13 +1479,14 @@ impl Default for DAG { #[test] fn test_simple_construction() { let mut g = DAG::new(); - let h0 = g.new_node(); + let subgraph_idx = SubgraphHandle::new(0); + let h0 = g.new_node(subgraph_idx); g.verify(); - let h1 = g.new_node(); - let h2 = g.new_node(); - let h3 = g.new_node(); - let h4 = g.new_node(); + let h1 = g.new_node(subgraph_idx); + let h2 = g.new_node(subgraph_idx); + let h3 = g.new_node(subgraph_idx); + let h4 = g.new_node(subgraph_idx); assert_ne!(h0, h1); assert_ne!(h1, h2); @@ -498,22 +1499,21 @@ fn test_simple_construction() { g.verify(); - let order = g.topological_sort(); - let levels = g.compute_levels(&order); - assert_eq!(order.len(), g.len()); + let levels = g.compute_level_container(); assert_eq!(levels.len(), g.len()); for i in 0..g.len() { - println!("{}) node {}, level {}", i, order[i].idx, levels[i]); + println!("{}), level {}", i, levels[i]); } } #[test] fn test_rank_api() { let mut g = DAG::new(); - let h0 = g.new_node(); - let h1 = g.new_node(); - let h2 = g.new_node(); + let subgraph_idx = SubgraphHandle::new(0); + let h0 = g.new_node(subgraph_idx); + let h1 = g.new_node(subgraph_idx); + let h2 = g.new_node(subgraph_idx); g.add_edge(h0, h1); g.add_edge(h1, h2); diff --git a/layout/src/core/geometry.rs b/layout/src/core/geometry.rs index b39f94c..4af7fca 100644 --- a/layout/src/core/geometry.rs +++ b/layout/src/core/geometry.rs @@ -504,6 +504,34 @@ impl Position { self.center = self.center.transpose(); self.halo = self.halo.transpose(); } + + pub fn set_min_x(&mut self, x: f64) { + let old_min_x = self.middle.x - self.size.x * 0.5; + let diff = x - old_min_x; + self.middle.x += diff * 0.5; + self.size.x -= diff; + } + + pub fn set_min_y(&mut self, y: f64) { + let old_min_y = self.middle.y - self.size.y * 0.5; + let diff = y - old_min_y; + self.middle.y += diff * 0.5; + self.size.y -= diff; + } + + pub fn set_max_x(&mut self, x: f64) { + let old_max_x = self.middle.x + self.size.x * 0.5; + let diff = x - old_max_x; + self.middle.x += diff * 0.5; + self.size.x += diff; + } + + pub fn set_max_y(&mut self, y: f64) { + let old_max_y = self.middle.y + self.size.y * 0.5; + let diff = y - old_max_y; + self.middle.y += diff * 0.5; + self.size.y += diff; + } } /// \return True if the segment intersects the rect. diff --git a/layout/src/gv/builder.rs b/layout/src/gv/builder.rs index 95d7494..dc55d77 100644 --- a/layout/src/gv/builder.rs +++ b/layout/src/gv/builder.rs @@ -2,6 +2,7 @@ use super::record::record_builder; use crate::adt::dag::NodeHandle; +use crate::adt::dag::SubgraphHandle; use crate::adt::map::ScopedMap; use crate::core::base::Orientation; use crate::core::color::Color; @@ -29,92 +30,173 @@ struct EdgeDesc { to_port: Option, } -/// This class constructs a visual graph from the parsed AST. -#[derive(Debug)] -pub struct GraphBuilder { - // This records the state of the top-level graph. +#[derive(Debug, Clone)] +pub struct SubgraphBuilder { global_state: PropertyList, - // This keeps track of the construction order of the nodes, because - // hashmap does not maintain a persistent iteration order. - node_order: Vec, - // Maps node names to their property list. - nodes: HashMap, - // A list of edge properties. - edges: Vec, - /// Scopes that maintain the property list that changes as we enter and - /// leave different regions of the graph. - global_attr: ScopedMap, - node_attr: ScopedMap, - edge_attr: ScopedMap, + container_order: Vec<(Vec, ContainerType)>, + name: String, + orientation: Orientation, } -impl Default for GraphBuilder { - fn default() -> Self { - Self::new() +impl SubgraphBuilder { + pub fn from_ast(name: String) -> Self { + let subgraph_builder = SubgraphBuilder { + global_state: PropertyList::new(), + name, + container_order: Vec::new(), + orientation: Orientation::TopToBottom, + }; + subgraph_builder } -} -impl GraphBuilder { - pub fn new() -> Self { - Self { - global_state: PropertyList::new(), - node_order: Vec::new(), - nodes: HashMap::new(), - edges: Vec::new(), - global_attr: ScopedMap::new(), - node_attr: ScopedMap::new(), - edge_attr: ScopedMap::new(), + pub fn get( + &self, + graph_builder: &GraphBuilder, + vg: &mut VisualGraph, + node_map: &mut HashMap, NodeHandle)>, + subgraph_map: &mut HashMap< + String, + (Vec, SubgraphHandle), + >, + ) { + for (subgraph_chain, name) in &self.container_order { + match name { + ContainerType::Node(name) => { + let node_prop = graph_builder.nodes.get(name).unwrap(); + let element = GraphBuilder::get_shape_from_attributes( + self.orientation, + node_prop, + name, + ); + let subgraph_chain = subgraph_chain + .iter() + .map(|x| subgraph_map.get(x).unwrap().1) + .collect::>(); + + let parent_subgraph_idx = + subgraph_chain.last().unwrap().clone(); + + let handle = vg.add_node(element, parent_subgraph_idx); + + node_map.insert(name.clone(), (subgraph_chain, handle)); + } + ContainerType::Subgraph(name) => { + let subgraph_builder = + graph_builder.subgraphs.get(name).unwrap(); + let subgraph_chain_handle = subgraph_chain + .iter() + .map(|x| subgraph_map.get(x).unwrap().1) + .collect::>(); + let parent_subgraph_idx = + subgraph_chain_handle.last().unwrap().clone(); + + // if subgraph is not cluster we only propagate attributes + // but do not create a new subgraph in the layout. + // this is because non cluster subgraphs do not affect positions of nodes. + if name.starts_with("cluster_") { + let element = + GraphBuilder::get_subgraph_shape_from_attributes( + self.orientation, + &subgraph_builder.global_state, + ); + let handle = + vg.add_subgraph(element, parent_subgraph_idx); + subgraph_map.insert( + name.clone(), + (subgraph_chain_handle, handle), + ); + } else { + subgraph_map.insert( + name.clone(), + ( + subgraph_chain_handle + [..subgraph_chain_handle.len() - 1] + .to_vec(), + parent_subgraph_idx, + ), + ); + } + + subgraph_builder.get( + graph_builder, + vg, + node_map, + subgraph_map, + ); + } + } } } - pub fn visit_graph(&mut self, graph: &ast::Graph) { - self.global_attr.push(); - self.node_attr.push(); - self.edge_attr.push(); + + pub fn visit_graph( + &mut self, + graph: &ast::Graph, + graph_builder: &mut GraphBuilder, + ) { + graph_builder.global_attr.push(); + graph_builder.node_attr.push(); + graph_builder.edge_attr.push(); + graph_builder.subgraph_stack.push(graph.name.clone()); for stmt in &graph.list.list { - self.visit_stmt(stmt); + self.visit_stmt(stmt, graph_builder); } + graph_builder.subgraph_stack.pop(); - // TODO: we dump the property list when we close the scope. This is not - // correct for sub graphs. - self.global_state = self.global_attr.flatten(); - - self.global_attr.pop(); - self.node_attr.pop(); - self.edge_attr.pop(); + self.global_state = graph_builder.global_attr.flatten(); + graph_builder.global_attr.pop(); + graph_builder.node_attr.pop(); + graph_builder.edge_attr.pop(); } - fn visit_stmt(&mut self, stmt: &ast::Stmt) { + + pub fn visit_stmt( + &mut self, + stmt: &ast::Stmt, + graph_builder: &mut GraphBuilder, + ) { match stmt { ast::Stmt::Edge(e) => { - self.visit_edge(e); + self.visit_edge(e, graph_builder); } ast::Stmt::Node(n) => { - self.visit_node(n); + self.visit_node(n, graph_builder); } ast::Stmt::Attribute(a) => { - self.visit_att(a); + self.visit_att(a, graph_builder); } - ast::Stmt::SubGraph(g) => { - self.visit_graph(g); + ast::Stmt::Subgraph(g) => { + let mut subgraph_builder = + SubgraphBuilder::from_ast(g.name.clone()); + self.container_order.push(( + graph_builder.subgraph_stack.clone(), + ContainerType::Subgraph(g.name.clone()), + )); + subgraph_builder.visit_graph(g, graph_builder); + graph_builder + .subgraphs + .insert(g.name.clone(), subgraph_builder); } } } - fn visit_edge(&mut self, e: &ast::EdgeStmt) { - self.edge_attr.push(); + pub fn visit_edge( + &mut self, + e: &ast::EdgeStmt, + graph_builder: &mut GraphBuilder, + ) { + graph_builder.edge_attr.push(); for att in e.list.iter() { - self.edge_attr.insert(&att.0, &att.1); + graph_builder.edge_attr.insert(&att.0, &att.1); } - self.init_node_with_name(&e.from.name, false); + graph_builder.init_node_with_name(&e.from.name, false, self); let mut prev = &e.from.name; for dest in &e.to { let curr = &dest.0.name; - self.init_node_with_name(curr, false); + graph_builder.init_node_with_name(curr, false, self); let has_arrow = matches!(dest.1, ast::ArrowKind::Arrow); - let prop_list = self.edge_attr.flatten(); - + let prop_list = graph_builder.edge_attr.flatten(); let edge = EdgeDesc { from: prev.clone(), to: curr.clone(), @@ -123,87 +205,161 @@ impl GraphBuilder { from_port: e.from.port.clone(), to_port: dest.0.port.clone(), }; - self.edges.push(edge); + graph_builder.edges.push(edge); prev = curr; } - self.edge_attr.pop(); - } - - // If \p overwrite is set then we are declaring a node. This means that - // we need to update the properties that already exist. - fn init_node_with_name(&mut self, name: &str, overwrite: bool) { - let node_attr = self.node_attr.flatten(); - - if let Option::Some(prop_list) = self.nodes.get_mut(name) { - if !overwrite { - return; - } - for p in node_attr { - prop_list.insert(p.0, p.1); - } - } else { - self.node_order.push(name.to_string()); - self.nodes.insert(name.to_string(), node_attr); - } + graph_builder.edge_attr.pop(); } - fn visit_node(&mut self, n: &ast::NodeStmt) { - self.node_attr.push(); + pub fn visit_node( + &mut self, + n: &ast::NodeStmt, + graph_builder: &mut GraphBuilder, + ) { + graph_builder.node_attr.push(); for att in n.list.iter() { - self.node_attr.insert(&att.0, &att.1); + graph_builder.node_attr.insert(&att.0, &att.1); } - self.init_node_with_name(&n.id.name, true); - self.node_attr.pop(); + graph_builder.init_node_with_name(&n.id.name, true, self); + graph_builder.node_attr.pop(); } - fn visit_att(&mut self, att: &ast::AttrStmt) { + pub fn visit_att( + &mut self, + att: &ast::AttrStmt, + graph_builder: &mut GraphBuilder, + ) { match att.target { ast::AttrStmtTarget::Graph => { for att in att.list.iter() { - self.global_attr.insert(&att.0, &att.1); + graph_builder.global_attr.insert(&att.0, &att.1); } } ast::AttrStmtTarget::Node => { for att in att.list.iter() { - self.node_attr.insert(&att.0, &att.1); + graph_builder.node_attr.insert(&att.0, &att.1); } } ast::AttrStmtTarget::Edge => { for att in att.list.iter() { - self.edge_attr.insert(&att.0, &att.1); + graph_builder.edge_attr.insert(&att.0, &att.1); } } } } +} + +#[derive(Debug, Clone)] +enum ContainerType { + Node(String), + Subgraph(String), +} + +/// This class constructs a visual graph from the parsed AST. +#[derive(Debug)] +pub struct GraphBuilder { + // This records the state of the top-level graph. + main_graph: SubgraphBuilder, + subgraph_stack: Vec, + // This keeps track of the construction order of the nodes, because + subgraphs: HashMap, + // hashmap does not maintain a persistent iteration order. + // Maps node names to their property list. + nodes: HashMap, + // A list of edge properties. + edges: Vec, + /// Scopes that maintain the property list that changes as we enter and + /// leave different regions of the graph. + global_attr: ScopedMap, + node_attr: ScopedMap, + edge_attr: ScopedMap, +} +impl Default for GraphBuilder { + fn default() -> Self { + Self::new() + } +} + +impl GraphBuilder { + pub fn new() -> Self { + let main_graph = SubgraphBuilder { + global_state: PropertyList::new(), + container_order: Vec::new(), + name: String::from("main"), + orientation: Orientation::TopToBottom, + }; + Self { + main_graph, + subgraphs: HashMap::new(), + subgraph_stack: Vec::new(), + nodes: HashMap::new(), + edges: Vec::new(), + global_attr: ScopedMap::new(), + node_attr: ScopedMap::new(), + edge_attr: ScopedMap::new(), + } + } + pub fn visit_graph(&mut self, graph: &ast::Graph) { + let mut main_graph = SubgraphBuilder::from_ast(graph.name.clone()); + main_graph.visit_graph(graph, self); + self.main_graph = main_graph; + } + + // If \p overwrite is set then we are declaring a node. This means that + // we need to update the properties that already exist. + fn init_node_with_name( + &mut self, + name: &str, + overwrite: bool, + subgraph_builder: &mut SubgraphBuilder, + ) { + let node_attr = self.node_attr.flatten(); + + if let Option::Some(prop_list) = self.nodes.get_mut(name) { + if !overwrite { + return; + } + for p in node_attr { + prop_list.insert(p.0, p.1); + } + } else { + subgraph_builder.container_order.push(( + self.subgraph_stack.clone(), + ContainerType::Node(name.to_string()), + )); + self.nodes.insert(name.to_string(), node_attr); + } + } pub fn get(&self) -> VisualGraph { let mut dir = Orientation::TopToBottom; // Set the graph orientation based on the 'rankdir' property. - if let Option::Some(rd) = self.global_state.get("rankdir") { + if let Option::Some(rd) = self.main_graph.global_state.get("rankdir") { if rd == "LR" { dir = Orientation::LeftToRight; } } + let element = GraphBuilder::get_subgraph_shape_from_attributes( + dir, + &self.main_graph.global_state, + ); - let mut vg = VisualGraph::new(dir); + let mut vg = VisualGraph::new(dir, element); // Keeps track of the newly created nodes and indexes them by name. - let mut node_map: HashMap = HashMap::new(); + let mut node_map = HashMap::new(); + let mut subgraph_map = HashMap::new(); - assert_eq!(self.nodes.len(), self.node_order.len()); - - // Create and register all of the nodes. - for node_name in self.node_order.iter() { - let node_prop = self.nodes.get(node_name).unwrap(); + subgraph_map.insert( + self.main_graph.name.clone(), + (Vec::new(), SubgraphHandle::new(0)), + ); - let shape = - Self::get_shape_from_attributes(dir, node_prop, node_name); - let handle = vg.add_node(shape); - node_map.insert(node_name.to_string(), handle); - } + self.main_graph + .get(self, &mut vg, &mut node_map, &mut subgraph_map); // Create and register all of the edges. for edge_prop in &self.edges { @@ -215,7 +371,8 @@ impl GraphBuilder { ); let from = node_map.get(&edge_prop.from).unwrap(); let to = node_map.get(&edge_prop.to).unwrap(); - vg.add_edge(shape, *from, *to); + + vg.add_edge(shape.clone(), from.1, to.1); } vg @@ -378,4 +535,61 @@ impl GraphBuilder { ); Element::create(shape, look, dir, sz) } + + fn get_subgraph_shape_from_attributes( + dir: Orientation, + lst: &PropertyList, + ) -> Element { + let mut edge_color = String::from("black"); + let mut fill_color = String::from("white"); + let mut font_size: usize = 14; + let mut line_width: usize = 1; + + if let Option::Some(x) = lst.get(&"color".to_string()) { + edge_color = x.clone(); + edge_color = Self::normalize_color(edge_color); + } + + if let Option::Some(style) = lst.get(&"style".to_string()) { + if style == "filled" && !lst.contains_key("fillcolor") { + fill_color = "lightgray".to_string(); + } + } + + if let Option::Some(x) = lst.get(&"fillcolor".to_string()) { + fill_color = x.clone(); + fill_color = Self::normalize_color(fill_color); + } + + if let Option::Some(fx) = lst.get(&"fontsize".to_string()) { + if let Result::Ok(x) = fx.parse::() { + font_size = x; + } else { + #[cfg(feature = "log")] + log::info!("Can't parse integer \"{}\"", fx); + } + } + + if let Option::Some(pw) = lst.get(&"width".to_string()) { + if let Result::Ok(x) = pw.parse::() { + line_width = x; + } else { + #[cfg(feature = "log")] + log::info!("Can't parse integer \"{}\"", pw); + } + } + + let look = StyleAttr::new( + Color::fast(&edge_color), + line_width, + Option::Some(Color::fast(&fill_color)), + 0, + font_size, + ); + let mut label = None; + if let Option::Some(val) = lst.get(&"label".to_string()) { + label = Some(val.to_string()); + } + Element::create_subgraph(dir, label, &look) + } } diff --git a/layout/src/gv/parser/ast.rs b/layout/src/gv/parser/ast.rs index 8caff9d..cf01635 100644 --- a/layout/src/gv/parser/ast.rs +++ b/layout/src/gv/parser/ast.rs @@ -29,7 +29,7 @@ impl AttributeList { self.list.push((from.to_string(), to.to_string())); } - pub fn iter(&self) -> std::slice::Iter<(String, String)> { + pub fn iter(&'_ self) -> std::slice::Iter<'_, (String, String)> { self.list.iter() } } @@ -108,12 +108,27 @@ impl EdgeStmt { } } +#[derive(Debug, Clone)] +pub struct Subgraph { + pub name: String, + pub list: StmtList, +} + +impl Subgraph { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + list: StmtList::new(), + } + } +} + #[derive(Debug, Clone)] pub enum Stmt { Edge(EdgeStmt), Node(NodeStmt), Attribute(AttrStmt), - SubGraph(Graph), + Subgraph(Graph), } // { ... } diff --git a/layout/src/gv/parser/lexer.rs b/layout/src/gv/parser/lexer.rs index aa894de..c5d23c3 100644 --- a/layout/src/gv/parser/lexer.rs +++ b/layout/src/gv/parser/lexer.rs @@ -125,7 +125,8 @@ impl Lexer { while self.has_next() { changed = true; self.read_char(); - if self.ch.is_ascii_control() { + // horizontal tab is allowed in comments and not ascii control + if self.ch.is_ascii_control() && self.ch != '\t' { self.read_char(); return changed; } diff --git a/layout/src/gv/parser/parser.rs b/layout/src/gv/parser/parser.rs index c61280f..6d2cbb5 100644 --- a/layout/src/gv/parser/parser.rs +++ b/layout/src/gv/parser/parser.rs @@ -47,7 +47,9 @@ impl DotParser { &mut self, is_subgraph: bool, ) -> Result { - let mut graph = ast::Graph::new(""); + let mut graph = ast::Graph::new( + format!("{}_anonymous_{}", self.lexer.pos, self.lexer.pos).as_str(), + ); // Handle the subgraph structure. if is_subgraph { @@ -166,13 +168,17 @@ impl DotParser { let ns = ast::Stmt::Node(ns); Result::Ok(ns) } + Token::SubgraphKW => { + let ns = ast::NodeStmt::new(id0); + let ns = ast::Stmt::Node(ns); + Result::Ok(ns) + } _ => to_error("Unsupported token"), } } Token::SubgraphKW => { let subgraph = self.parse_graph(true)?; - let ns = ast::Stmt::SubGraph(subgraph); - Result::Ok(ns) + Result::Ok(ast::Stmt::Subgraph(subgraph)) } //attr_stmt : (graph | node | edge) attr_list Token::GraphKW => { @@ -197,9 +203,13 @@ impl DotParser { Token::OpenBrace => { // Handle anonymous scopes: self.lex(); - let mut graph = ast::Graph::new("anonymous"); + // let mut graph = ast::Graph::new("anonymous"); + let mut graph = ast::Graph::new( + format!("{}_anonymous_{}", self.lexer.pos, self.lexer.pos) + .as_str(), + ); graph.list = self.parse_stmt_list()?; - Result::Ok(ast::Stmt::SubGraph(graph)) + Result::Ok(ast::Stmt::Subgraph(graph)) } _ => to_error("Unknown token"), diff --git a/layout/src/gv/parser/printer.rs b/layout/src/gv/parser/printer.rs index 75007dd..6d32e05 100644 --- a/layout/src/gv/parser/printer.rs +++ b/layout/src/gv/parser/printer.rs @@ -71,7 +71,7 @@ fn print_stmt(stmt: &ast::Stmt, indent: usize) { ast::Stmt::Attribute(a) => { print_att(a, indent); } - ast::Stmt::SubGraph(g) => { + ast::Stmt::Subgraph(g) => { print_graph(g, indent); } } @@ -79,7 +79,7 @@ fn print_stmt(stmt: &ast::Stmt, indent: usize) { fn print_graph(graph: &ast::Graph, indent: usize) { print!("{}", " ".repeat(indent)); - println!("Graph: {}", graph.name); + println!("Graph: {:?}", graph.name); for stmt in &graph.list.list { print_stmt(stmt, indent + 1); } diff --git a/layout/src/lib.rs b/layout/src/lib.rs index 38bf929..ed0873f 100644 --- a/layout/src/lib.rs +++ b/layout/src/lib.rs @@ -69,10 +69,20 @@ fn simple_graph() { use layout::std_shapes::shapes::*; use layout::topo::layout::VisualGraph; use layout::topo::placer::Placer; + use layout::core::color::Color; use std::fs; + use layout::adt::dag::SubgraphHandle; // Create a new graph: - let mut vg = VisualGraph::new(Orientation::LeftToRight); + let mut vg = VisualGraph::new( + Orientation::LeftToRight, + Element::create( + ShapeKind::Frame(Some("Main Graph".to_string())), + StyleAttr::new(Color::transparent(), 0, None, 0, 0), + Orientation::LeftToRight, + Point::zero(), + ), + ); // Define the node styles: let sp0 = ShapeKind::new_box("one"); @@ -85,8 +95,9 @@ fn simple_graph() { let node1 = Element::create(sp1, look1, Orientation::LeftToRight, sz); // Add the nodes to the graph, and save a handle to each node. - let handle0 = vg.add_node(node0); - let handle1 = vg.add_node(node1); + let main_subgraph_handle = SubgraphHandle::new(0); + let handle0 = vg.add_node(node0, main_subgraph_handle); + let handle1 = vg.add_node(node1, main_subgraph_handle); // Add an edge between the nodes. let arrow = Arrow::simple("123"); diff --git a/layout/src/std_shapes/render.rs b/layout/src/std_shapes/render.rs index b818278..9001ded 100644 --- a/layout/src/std_shapes/render.rs +++ b/layout/src/std_shapes/render.rs @@ -309,6 +309,27 @@ impl Renderable for Element { ); canvas.draw_text(self.pos.center(), text.as_str(), &self.look); } + ShapeKind::Frame(text) => { + canvas.draw_rect( + self.pos.bbox(false).0, + self.pos.size(false), + &self.look, + self.properties.clone(), + Option::None, + ); + if let Some(txt_str) = text { + let text_size = + get_size_for_str(txt_str, self.look.font_size); + let x_middle = self.pos.middle().x; + let text_pos = Point::new( + x_middle - text_size.x * 0., + self.pos.bbox(false).0.y + + text_size.y / 2. + + BORDER_PADDING / 2., + ); + canvas.draw_text(text_pos, txt_str.as_str(), &self.look); + } + } ShapeKind::Circle(text) => { canvas.draw_circle( self.pos.center(), diff --git a/layout/src/std_shapes/shapes.rs b/layout/src/std_shapes/shapes.rs index f159344..81160ef 100644 --- a/layout/src/std_shapes/shapes.rs +++ b/layout/src/std_shapes/shapes.rs @@ -12,6 +12,8 @@ use crate::std_shapes::render::get_shape_size; const PADDING: f64 = 60.; const CONN_PADDING: f64 = 10.; +pub(crate) const BORDER_PADDING: f64 = 5.; + #[derive(Debug, Copy, Clone)] pub enum LineEndKind { None, @@ -43,6 +45,7 @@ pub enum ShapeKind { DoubleCircle(String), Record(RecordDef), Connector(Option), + Frame(Option), } impl ShapeKind { @@ -126,6 +129,40 @@ impl Element { } } + pub fn create_subgraph( + orientation: Orientation, + label: Option, + look: &StyleAttr, + ) -> Element { + Element { + shape: ShapeKind::Frame(label), + look: look.clone(), + orientation, + pos: Position::new( + Point::new(25., 25.), + Point::new(20., 20.), + Point::new(25., 25.), + Point::splat(PADDING), + ), + properties: Option::None, + } + } + + pub fn create_border() -> Element { + Element { + shape: ShapeKind::None, + look: StyleAttr::simple(), + orientation: Orientation::TopToBottom, + pos: Position::new( + Point::zero(), + Point::zero(), + Point::zero(), + Point::splat(BORDER_PADDING), + ), + properties: Option::None, + } + } + pub fn empty_connector(dir: Orientation) -> Element { Self::create_connector("", &StyleAttr::simple(), dir) } diff --git a/layout/src/topo/layout.rs b/layout/src/topo/layout.rs index 4f78248..30f2202 100644 --- a/layout/src/topo/layout.rs +++ b/layout/src/topo/layout.rs @@ -12,11 +12,14 @@ use crate::core::base::Orientation; use crate::core::format::RenderBackend; use crate::core::format::Renderable; use crate::core::format::Visible; +use crate::core::geometry::get_size_for_str; +use crate::core::geometry::Point; use crate::core::geometry::Position; use crate::std_shapes::render::*; use crate::std_shapes::shapes::*; use crate::topo::optimizer::EdgeCrossOptimizer; use crate::topo::optimizer::RankOptimizer; +use crate::topo::sander; use std::mem::swap; use std::vec; @@ -26,6 +29,8 @@ use super::placer::Placer; pub struct VisualGraph { // Holds all of the elements in the graph. nodes: Vec, + // Holds all of the subgraphs in the graph. + subgraphs: Vec, // The arrows and the list of elements that they visits. edges: Vec<(Arrow, Vec)>, // Contains a list of self-edges. We use this as a temporary storage during @@ -42,9 +47,10 @@ pub struct VisualGraph { } impl VisualGraph { - pub fn new(orientation: Orientation) -> Self { + pub fn new(orientation: Orientation, main_graph_selem: Element) -> Self { VisualGraph { nodes: Vec::new(), + subgraphs: vec![main_graph_selem], edges: Vec::new(), self_edges: Vec::new(), dag: DAG::new(), @@ -80,6 +86,26 @@ impl VisualGraph { self.element_mut(n).position_mut() } + pub fn pos_sg(&self, sg: SubgraphHandle) -> Position { + self.subgraphs[sg.get_index()].position() + } + + pub fn pos_sg_mut(&mut self, sg: SubgraphHandle) -> &mut Position { + self.subgraphs[sg.get_index()].position_mut() + } + + pub fn size_sg_label(&self, sg: SubgraphHandle) -> Point { + let font_size = self.subgraphs[sg.get_index()].look.font_size; + match &self.subgraphs[sg.get_index()].shape { + // ShapeKind::Frame(label) => get_size_for_str(label, font_size), + ShapeKind::Frame(label) => match label { + Some(l) => get_size_for_str(l, font_size), + None => Point::zero(), + }, + _ => panic!("Subgraph does not have a label!"), + } + } + pub fn is_connector(&self, n: NodeHandle) -> bool { return self.element(n).is_connector(); } @@ -100,13 +126,39 @@ impl VisualGraph { /// Add a node to the graph. /// \returns a handle to the node. - pub fn add_node(&mut self, elem: Element) -> NodeHandle { - let res = self.dag.new_node(); + pub fn add_node( + &mut self, + elem: Element, + parent_subgraph_idx: SubgraphHandle, + ) -> NodeHandle { + let res = self.dag.new_node(parent_subgraph_idx); assert!(res.get_index() == self.nodes.len()); self.nodes.push(elem); res } + pub fn add_connector_node( + &mut self, + elem: Element, + from: NodeHandle, + to: NodeHandle, + ) -> NodeHandle { + let res = self.dag.new_connector_node(from, to); + assert!(res.get_index() == self.nodes.len()); + self.nodes.push(elem); + res + } + + /// add border + pub(crate) fn add_vertical_border(&mut self, lvl_s: usize, lvl_e: usize) { + (lvl_s..=lvl_e).for_each(|_| { + let elem_l = Element::create_border(); + let elem_r = Element::create_border(); + self.nodes.push(elem_l); + self.nodes.push(elem_r); + }); + } + /// Add an edge to the graph. pub fn add_edge(&mut self, arrow: Arrow, from: NodeHandle, to: NodeHandle) { assert!(from.get_index() < self.nodes.len(), "Invalid handle"); @@ -114,11 +166,24 @@ impl VisualGraph { let lst = vec![from, to]; self.edges.push((arrow, lst)); } + + pub fn add_subgraph( + &mut self, + elem: Element, + parent_subgraph_idx: SubgraphHandle, + ) -> SubgraphHandle { + let res = self.dag.new_subgraph(parent_subgraph_idx); + self.subgraphs.push(elem); + res + } } // Render. impl VisualGraph { fn render(&self, debug: bool, rb: &mut dyn RenderBackend) { + for subgraph in &self.subgraphs { + subgraph.render(debug, rb); + } // Draw the nodes. for node in &self.nodes { node.render(debug, rb); @@ -143,7 +208,12 @@ impl VisualGraph { disable_layout: bool, rb: &mut dyn RenderBackend, ) { + // pass if nodes are empty + if self.dag.is_empty() { + return; + } self.lower(disable_opt); + sander::do_it(self); Placer::new(self).layout(disable_layout); self.render(debug_mode, rb); } @@ -201,7 +271,7 @@ impl VisualGraph { /// This is the second step of graph canonicalization. pub fn split_text_edges(&mut self) { let mut edges = self.edges.clone(); - //self.edge_list.clear(); + self.edges.clear(); for edge in edges.iter_mut() { let lst = &edge.1; @@ -220,7 +290,7 @@ impl VisualGraph { // Create a new connection block. let dir = self.element(from).orientation; let conn = Element::create_connector(&text, &arrow.look, dir); - let conn = self.add_node(conn); + let conn = self.add_connector_node(conn, from, to); // Update the edge node list, and remove the text. edge.1 = vec![from, conn, to]; @@ -261,8 +331,7 @@ impl VisualGraph { let curr_level = self.dag.level(curr); // If the edges point to a lower rank then move on. - assert!(prev_level < curr_level, "Invalid edge"); - if prev_level + 1 == curr_level { + if prev_level + 1 >= curr_level { i += 1; continue; } @@ -270,7 +339,7 @@ impl VisualGraph { // We need to add a new connector node. let dir = self.element(prev).orientation; let conn = Element::empty_connector(dir); - let conn = self.add_node(conn); + let conn = self.add_connector_node(conn, prev, curr); lst.insert(i, conn); // Update the dag connections. @@ -302,7 +371,8 @@ impl VisualGraph { arrow.text = String::new(); let dir = self.element(node).orientation; let conn = Element::create_connector(&text, &arrow.look, dir); - let conn = self.add_node(conn); + let conn = + self.add_node(conn, self.dag.get_parent_subgraph_index_n(node)); self.dag.update_node_rank_level(conn, level, Some(node)); self.edges.push((arrow, vec![node, conn, node])); } diff --git a/layout/src/topo/mod.rs b/layout/src/topo/mod.rs index b6b5b3e..3de826f 100644 --- a/layout/src/topo/mod.rs +++ b/layout/src/topo/mod.rs @@ -3,3 +3,4 @@ pub mod layout; pub mod optimizer; pub mod placer; +pub(crate) mod sander; diff --git a/layout/src/topo/optimizer.rs b/layout/src/topo/optimizer.rs index 147b9e9..ba907cc 100644 --- a/layout/src/topo/optimizer.rs +++ b/layout/src/topo/optimizer.rs @@ -70,7 +70,9 @@ impl<'a> EdgeCrossOptimizer<'a> { pub fn rotate_rank(&mut self) { for i in 0..self.dag.num_levels() { let row = self.dag.row_mut(i); - row.rotate_left(1); + if row.len() >= 1 { + row.rotate_left(1); + } } } @@ -224,7 +226,11 @@ impl<'a> RankOptimizer<'a> { Self { dag } } - pub fn try_to_sink_node(&mut self, node: NodeHandle) -> bool { + pub fn try_to_sink_node( + &mut self, + node: NodeHandle, + max_level: usize, + ) -> bool { let backs = self.dag.predecessors(node); let fwds = self.dag.successors(node); @@ -241,10 +247,14 @@ impl<'a> RankOptimizer<'a> { highest_next = highest_next.min(next_rank); } + if highest_next == 0 { + return false; + } + let target_rank = (highest_next - 1).min(max_level); + // We found an opportunity to sink a node. - if highest_next > curr_rank + 1 { - self.dag - .update_node_rank_level(node, highest_next - 1, None); + if target_rank > curr_rank { + self.dag.update_node_rank_level(node, target_rank, None); return true; } false @@ -261,10 +271,14 @@ impl<'a> RankOptimizer<'a> { #[cfg(feature = "log")] let mut iter = 0; + let subgraph_levels = self.dag.get_subgraph_levels(); + loop { let mut c = 0; for node in self.dag.iter() { - if self.try_to_sink_node(node) { + let (_, max_lvl) = subgraph_levels + [self.dag.get_parent_subgraph_index_n(node).get_index()]; + if self.try_to_sink_node(node, max_lvl) { c += 1; } } diff --git a/layout/src/topo/placer/bk.rs b/layout/src/topo/placer/bk.rs index 20b4be4..a416f9f 100644 --- a/layout/src/topo/placer/bk.rs +++ b/layout/src/topo/placer/bk.rs @@ -1,12 +1,30 @@ //! This module implements block placement that's based on the Brandes and Kopf //! paper "Fast and Simple Horizontal Coordinate Assignment." -use crate::adt::dag::NodeHandle; +use super::simple; +use crate::adt::dag::{NodeHandle, SubgraphHandle}; use crate::core::geometry::weighted_median; use crate::topo::layout::VisualGraph; use std::collections::HashSet; +use std::iter::Iterator; +use std::iter::Rev; -use super::simple; +#[derive(Clone)] +enum RangeEither { + Range(std::ops::Range), + RangeRev(Rev>), +} + +impl Iterator for RangeEither { + type Item = usize; + + fn next(&mut self) -> Option { + match self { + RangeEither::Range(range) => range.next(), + RangeEither::RangeRev(range) => range.next(), + } + } +} #[derive(Debug, Clone, Copy)] enum OrderLR { @@ -14,6 +32,12 @@ enum OrderLR { RightToLeft, } +#[derive(Debug, Clone, Copy)] +enum OrderTB { + TopToBottom, + BottomToTop, +} + impl OrderLR { fn is_left_to_right(&self) -> bool { match self { @@ -29,13 +53,20 @@ struct NodeAttachInfo { above: Vec>, /// For each node, marks which node in the row below aligns to it. below: Vec>, + order_tb: OrderTB, + order_lr: OrderLR, } impl NodeAttachInfo { - fn new(size: usize) -> Self { + fn new(size: usize, order_tb: OrderTB, order_lr: OrderLR) -> Self { let above = vec![None; size]; let below = vec![None; size]; - Self { above, below } + Self { + above, + below, + order_tb, + order_lr, + } } /// Align the node \p from to \p to. @@ -60,12 +91,18 @@ impl NodeAttachInfo { /// relationship that is expressed in this data-structure. fn get_verticals(&mut self) -> VerticalList { // The list of constructed verticals. - let mut res = VerticalList::new(); + let mut res = VerticalList::new(self.order_tb, self.order_lr); // The list of used nodes. let mut used: Vec = vec![false; self.above.len()]; + let idx_range = match self.order_tb { + OrderTB::TopToBottom => RangeEither::Range(0..self.above.len()), + OrderTB::BottomToTop => { + RangeEither::RangeRev((0..self.above.len()).rev()) + } + }; // For each node in the graph: - for i in 0..self.above.len() { + for i in idx_range { let mut vertical: Vec = Vec::new(); // Don't visit visited nodes. @@ -93,7 +130,7 @@ impl NodeAttachInfo { vertical.push(NodeHandle::from(idx)); } used[idx] = true; - res.push(vertical); + res.push_vertical(vertical); } res @@ -110,14 +147,17 @@ struct Scheduler<'a> { // For ech row, saves the end point of the last box. last_x_for_row: Vec, // The node placement order (left to right, or right to left). - order: OrderLR, + order_lr: OrderLR, + order_tb: OrderTB, } impl<'a> Scheduler<'a> { - fn new(vg: &'a VisualGraph, vl: VerticalList, order: OrderLR) -> Self { + fn new(vg: &'a VisualGraph, vl: VerticalList) -> Self { + let order_lr = vl.order_lr; + let order_tb = vl.order_tb; let xs = vec![0.; vg.num_nodes()]; let idx = vec![0; vg.dag.num_levels()]; - let v = if order.is_left_to_right() { + let v = if order_lr.is_left_to_right() { f64::NEG_INFINITY } else { f64::INFINITY @@ -129,7 +169,8 @@ impl<'a> Scheduler<'a> { x_coordinates: xs, sched_idx: idx, last_x_for_row, - order, + order_lr, + order_tb, } } @@ -138,22 +179,27 @@ impl<'a> Scheduler<'a> { } fn schedule(&mut self) { - for v in &self.vl { + for v in &self.vl.list { self.verify_vertical(v); } let mut to_place = self.vl.len(); - + let idx_range = match self.order_tb { + OrderTB::TopToBottom => RangeEither::Range(0..self.vl.len()), + OrderTB::BottomToTop => { + RangeEither::RangeRev((0..self.vl.len()).rev()) + } + }; while to_place > 0 { - for i in 0..self.vl.len() { + for i in idx_range.clone() { if !self.is_vertical_ready(i) { continue; } // Place the nodes. - let x = self.first_schedule_x(&self.vl[i]); + let x = self.first_schedule_x(&self.vl.list[i]); self.place_vertical(i, x); // Wipe the vertical. - self.vl[i].clear(); + self.vl.list[i].clear(); to_place -= 1; } } @@ -167,13 +213,13 @@ impl<'a> Scheduler<'a> { let last = self.last_x_for_row[level]; let pos = self.vg.pos(*elem); - let offset = if self.order.is_left_to_right() { + let offset = if self.order_lr.is_left_to_right() { pos.distance_to_left(true) } else { pos.distance_to_right(true) }; - if self.order.is_left_to_right() { + if self.order_lr.is_left_to_right() { last_offset_x = last_offset_x.max(last + offset); } else { last_offset_x = last_offset_x.min(last - offset); @@ -184,14 +230,14 @@ impl<'a> Scheduler<'a> { // Place the nodes in the vertical into the schedule. fn place_vertical(&mut self, i: usize, center_x: f64) { - let v = &self.vl[i]; + let v = &self.vl.list[i]; for elem in v { // Record the x coordinate for the vertical. self.x_coordinates[elem.get_index()] = center_x; // Update the last x value for the row. let level = self.vg.dag.level(*elem); let pos = self.vg.pos(*elem); - if self.order.is_left_to_right() { + if self.order_lr.is_left_to_right() { let side_x = pos.distance_to_right(true); self.last_x_for_row[level] = center_x + side_x; } else { @@ -199,6 +245,11 @@ impl<'a> Scheduler<'a> { self.last_x_for_row[level] = center_x - side_x; } self.sched_idx[level] += 1; + let layer_length = self.vg.dag.row(level).len(); + assert!( + self.sched_idx[level] <= layer_length, + "sched_idx out of bounds" + ); } } @@ -221,7 +272,7 @@ impl<'a> Scheduler<'a> { let len = row.len(); if first_free < len { - return if self.order.is_left_to_right() { + return if self.order_lr.is_left_to_right() { row[first_free] == node } else { row[len - first_free - 1] == node @@ -233,7 +284,7 @@ impl<'a> Scheduler<'a> { /// \returns True if the vertical \p idx is ready for scheduling (if all of /// the dependencies are met). fn is_vertical_ready(&self, idx: usize) -> bool { - let vert = &self.vl[idx]; + let vert = &self.vl.list[idx]; if vert.is_empty() { return false; } @@ -251,6 +302,13 @@ pub struct BK<'a> { vg: &'a mut VisualGraph, } +#[derive(Debug, Clone)] +enum MedianNodes { + None, + Single(NodeHandle), + Double(NodeHandle, NodeHandle), +} + // A set of edges between two nodes in the graph. type EdgeSet = HashSet<(NodeHandle, NodeHandle)>; // Represents an edge between two rows (index of the element in the row). @@ -258,7 +316,29 @@ type EdgeIdxs = (usize, usize); // A list of nodes that are vertically aligned. type Vertical = Vec; // Represents a list of nodes that needs to be scheduled vertically. -type VerticalList = Vec; +struct VerticalList { + list: Vec, + order_tb: OrderTB, + order_lr: OrderLR, +} + +impl VerticalList { + fn new(order_tb: OrderTB, order_lr: OrderLR) -> Self { + Self { + list: Vec::new(), + order_tb, + order_lr, + } + } + + fn push_vertical(&mut self, v: Vertical) { + self.list.push(v); + } + + fn len(&self) -> usize { + self.list.len() + } +} impl<'a> BK<'a> { pub(crate) fn new(vg: &'a mut VisualGraph) -> Self { @@ -284,9 +364,16 @@ impl<'a> BK<'a> { /// Compute and return a list of successor edges that don't cross the /// internal edges (edges between connection nodes). - fn get_valid_edges(&self) -> EdgeSet { + fn get_valid_edges(&self, order_tb: OrderTB) -> EdgeSet { let mut valid_edges: EdgeSet = EdgeSet::new(); - for i in 0..self.vg.dag.num_levels() - 1 { + let num_levels = self.vg.dag.num_levels(); + let idx_range = match order_tb { + OrderTB::TopToBottom => RangeEither::Range(0..num_levels - 1), + OrderTB::BottomToTop => { + RangeEither::RangeRev((0..num_levels - 1).rev()) + } + }; + for i in idx_range { let r0 = self.vg.dag.row(i); let r1 = self.vg.dag.row(i + 1); let edges = self.extract_edges_with_no_type2_conflict(r0, r1); @@ -294,6 +381,26 @@ impl<'a> BK<'a> { valid_edges.insert(e); } } + // insert every edge in borders + for s in 0..self.vg.dag.num_subgraphs() { + let left_borders = + self.vg.dag.subgraph_left_borders(SubgraphHandle::new(s)); + let left_borders_len = left_borders.len(); + for i in 1..left_borders_len { + let from = left_borders[i - 1]; + let to = left_borders[i]; + valid_edges.insert((from, to)); + } + let right_borders = + self.vg.dag.subgraph_right_borders(SubgraphHandle::new(s)); + let right_borders_len = right_borders.len(); + for i in 1..right_borders_len { + let from = right_borders[i - 1]; + let to = right_borders[i]; + valid_edges.insert((from, to)); + } + } + valid_edges } @@ -306,18 +413,22 @@ impl<'a> BK<'a> { ) -> Vec<(NodeHandle, NodeHandle)> { let mut regular_edges: Vec = Vec::new(); let mut strong_edges: Vec = Vec::new(); + let mut border_edges: Vec = Vec::new(); // For each node in R0: for (idx0, elem) in r0.iter().enumerate() { // For each successor: for succ in self.vg.succ(*elem) { - // Check if and where it points to in R1. (we could have - // same-row self-edges). if let Option::Some(idx1) = r1.iter().position(|&r| r == *succ) { // Figure out if this is a strong edge or a regular edge. let c0 = self.vg.is_connector(*elem); let c1 = self.vg.is_connector(*succ); - if c0 && c1 { + + let b0 = self.vg.dag.is_vertical_border(*elem); + let b1 = self.vg.dag.is_vertical_border(*succ); + if b0 && b1 { + border_edges.push((idx0, idx1)); + } else if c0 && c1 { strong_edges.push((idx0, idx1)); } else { regular_edges.push((idx0, idx1)); @@ -327,24 +438,32 @@ impl<'a> BK<'a> { } let mut res: Vec<(NodeHandle, NodeHandle)> = Vec::new(); - 'outer: for reg in regular_edges.iter() { - for strong in strong_edges.iter() { - // Check if there is no conflict. - if Self::are_edges_crossing(*reg, *strong) { - // Continue to the next strong edges. - continue; - } - - // Found a conflict, we must not register this edge. - continue 'outer; - } + for reg in regular_edges.iter() { + let conflict1 = strong_edges + .iter() + .any(|strong| Self::are_edges_crossing(*reg, *strong)); + let conflict2 = border_edges + .iter() + .any(|border| Self::are_edges_crossing(*reg, *border)); // None of the strong edges conflicted with the regular edge. - res.push((r0[reg.0], r1[reg.1])); + if !conflict1 && !conflict2 { + res.push((r0[reg.0], r1[reg.1])); + } } - - // Now also add the strong edges. + // turn above into iter inside iter + let conflict = border_edges.iter().any(|border| { + strong_edges + .iter() + .any(|strong| Self::are_edges_crossing(*strong, *border)) + }); + assert!(!conflict, "strong edge conflicts with border edge"); + + // add border edges. + for border in border_edges { + res.push((r0[border.0], r1[border.1])); + } + // add strong edges for strong in strong_edges { - // None of the strong edges conflicted with the regular edge. res.push((r0[strong.0], r1[strong.1])); } res @@ -353,12 +472,12 @@ impl<'a> BK<'a> { /// Computes the median of the predecessors, considering only allowed edges. /// Returns a list of x coordinates, for each node in the graph. If the node /// has no predecessors then the procedure returns the value zero. - fn get_pred_medians(&self, valid_edges: EdgeSet) -> Vec { + fn get_neighbour_medians(&self, valid_edges: EdgeSet) -> Vec { // Builds the median of preds for each node. - let mut res: Vec = Vec::new(); + let mut res = Vec::new(); // Collect a list of the pred's x coordinates. - let mut pos_list: Vec = Vec::new(); + let mut pos_list = Vec::new(); // For each node. for node in self.vg.iter_nodes() { @@ -371,15 +490,31 @@ impl<'a> BK<'a> { if !valid_edges.contains(&(*pred, node)) { continue; } - let pos = self.vg.pos(*pred).center().x; - pos_list.push(pos) + pos_list.push(*pred) } + pos_list.sort_by(|a, b| { + self.vg + .pos(*a) + .center() + .x + .partial_cmp(&self.vg.pos(*b).center().x) + .unwrap() + }); // Merge all of the predecessors into one median value. if pos_list.is_empty() { - res.push(0.); + res.push(MedianNodes::None); } else { - res.push(weighted_median(&pos_list)); + if pos_list.len() % 2 == 0 { + let mid = pos_list.len() / 2; + res.push(MedianNodes::Double( + pos_list[mid - 1], + pos_list[mid], + )); + } else { + let mid = pos_list.len() / 2; + res.push(MedianNodes::Single(pos_list[mid])); + } } } res @@ -390,18 +525,30 @@ impl<'a> BK<'a> { (0..vec.len()).find(|&i| vec[i] == elem) } - fn compute_alignment(&self, order: OrderLR) -> NodeAttachInfo { + fn compute_alignment( + &self, + order_lr: OrderLR, + order_tb: OrderTB, + ) -> NodeAttachInfo { let num = self.vg.num_nodes(); - let mut align_info = NodeAttachInfo::new(num); + let mut align_info = NodeAttachInfo::new(num, order_tb, order_lr); // Computes important edges (with no type2 conflicts). - let valid_edges = self.get_valid_edges(); + let valid_edges = self.get_valid_edges(order_tb); // The desired medians for each node in the graph. - let medians: Vec = self.get_pred_medians(valid_edges); + let medians = self.get_neighbour_medians(valid_edges); + + let idx_range = match order_tb { + OrderTB::TopToBottom => { + RangeEither::Range(0..self.vg.dag.num_levels() - 1) + } + OrderTB::BottomToTop => { + RangeEither::RangeRev((0..self.vg.dag.num_levels() - 1).rev()) + } + }; - for i in 0..self.vg.dag.num_levels() - 1 { - // The row above. + for i in idx_range { let mut r0 = self.vg.dag.row(i).clone(); // The current row. let mut r1 = self.vg.dag.row(i + 1).clone(); @@ -410,36 +557,46 @@ impl<'a> BK<'a> { // Simulate searching from the right by reversing the order of the // edges, and the order of the collisions. - if !order.is_left_to_right() { + if !order_lr.is_left_to_right() { r1.reverse(); r0.reverse(); } for node in r1 { - let node_x = medians[node.get_index()]; let mut best_idx: Option = None; - let mut best_delta = f64::INFINITY; - - // Scan the predecessors: - for pred in self.vg.preds(node) { - let idx; - // Search for the index of the predecessor in the row. - if let Some(idx_in_row) = Self::index_of(*pred, &r0) { - idx = idx_in_row; - } else { - continue; - } - // Don't mess with nodes that are taken. - if used[idx] { - continue; + match medians[node.get_index()] { + MedianNodes::None => {} + MedianNodes::Single(pred) => { + // Find the index of the predecessor in the row. + if let Some(idx_in_row) = Self::index_of(pred, &r0) { + // Don't mess with nodes that are taken. + if !used[idx_in_row] { + best_idx = Some(idx_in_row); + } + } } - - // Of the remaining edges, select the closest one. - let delta = (self.vg.pos(*pred).center().x - node_x).abs(); - if delta < best_delta { - best_idx = Some(idx); - best_delta = delta; + MedianNodes::Double(pred1, pred2) => { + // traversal of the predecessors is done in the order of the + // alignment, so we need to reverse if necessary. + let (pred1, pred2) = if order_lr.is_left_to_right() { + (pred1, pred2) + } else { + (pred2, pred1) + }; + if let Some(idx_in_row) = Self::index_of(pred1, &r0) { + // Don't mess with nodes that are taken. + if !used[idx_in_row] { + best_idx = Some(idx_in_row); + } else if let Some(idx_in_row) = + Self::index_of(pred2, &r0) + { + // Don't mess with nodes that are taken. + if !used[idx_in_row] { + best_idx = Some(idx_in_row); + } + } + } } } @@ -458,27 +615,37 @@ impl<'a> BK<'a> { } pub(crate) fn do_it(&mut self) { - let vl = self.compute_alignment(OrderLR::RightToLeft).get_verticals(); - let mut sc0 = Scheduler::new(self.vg, vl, OrderLR::RightToLeft); + let vl = self + .compute_alignment(OrderLR::RightToLeft, OrderTB::BottomToTop) + .get_verticals(); + let mut sc0 = Scheduler::new(self.vg, vl); sc0.schedule(); - let vl = self.compute_alignment(OrderLR::RightToLeft).get_verticals(); - let mut sc1 = Scheduler::new(self.vg, vl, OrderLR::LeftToRight); - sc1.schedule(); - let vl = self.compute_alignment(OrderLR::LeftToRight).get_verticals(); - let mut sc2 = Scheduler::new(self.vg, vl, OrderLR::RightToLeft); - sc2.schedule(); - let vl = self.compute_alignment(OrderLR::LeftToRight).get_verticals(); - let mut sc3 = Scheduler::new(self.vg, vl, OrderLR::LeftToRight); - sc3.schedule(); - let xs0 = sc0.get_x_placement(); + + let vl = self + .compute_alignment(OrderLR::RightToLeft, OrderTB::TopToBottom) + .get_verticals(); + let mut sc1 = Scheduler::new(self.vg, vl); + sc1.schedule(); let xs1 = sc1.get_x_placement(); + + let vl = self + .compute_alignment(OrderLR::LeftToRight, OrderTB::BottomToTop) + .get_verticals(); + let mut sc2 = Scheduler::new(self.vg, vl); + sc2.schedule(); let xs2 = sc2.get_x_placement(); + + let vl = self + .compute_alignment(OrderLR::LeftToRight, OrderTB::TopToBottom) + .get_verticals(); + let mut sc3 = Scheduler::new(self.vg, vl); + sc3.schedule(); let xs3 = sc3.get_x_placement(); - for i in 0..xs0.len() { + for i in 0..xs3.len() { let node = NodeHandle::from(i); - let val = (xs0[i] + xs1[i] + xs2[i] + xs3[i]) / 4.0; + let val = weighted_median(&[xs0[i], xs1[i], xs2[i], xs3[i]]); self.vg.pos_mut(node).set_x(val); } @@ -504,7 +671,8 @@ fn edge_crossing() { #[test] fn test_extract_verticals() { - let mut ai = NodeAttachInfo::new(6); + let mut ai = + NodeAttachInfo::new(6, OrderTB::TopToBottom, OrderLR::LeftToRight); ai.add(NodeHandle::new(0), NodeHandle::new(1)); ai.add(NodeHandle::new(1), NodeHandle::new(2)); ai.add(NodeHandle::new(2), NodeHandle::new(3)); @@ -512,11 +680,12 @@ fn test_extract_verticals() { let verticals = ai.get_verticals(); + let vertical_list = &verticals.list; assert_eq!(verticals.len(), 2); - assert_eq!(verticals[0][0].get_index(), 0); - assert_eq!(verticals[0][1].get_index(), 1); - assert_eq!(verticals[0][2].get_index(), 2); - assert_eq!(verticals[0][3].get_index(), 3); - assert_eq!(verticals[1][0].get_index(), 4); - assert_eq!(verticals[1][1].get_index(), 5); + assert_eq!(vertical_list[0][0].get_index(), 0); + assert_eq!(vertical_list[0][1].get_index(), 1); + assert_eq!(vertical_list[0][2].get_index(), 2); + assert_eq!(vertical_list[0][3].get_index(), 3); + assert_eq!(vertical_list[1][0].get_index(), 4); + assert_eq!(vertical_list[1][1].get_index(), 5); } diff --git a/layout/src/topo/placer/place.rs b/layout/src/topo/placer/place.rs index 269f040..c609958 100644 --- a/layout/src/topo/placer/place.rs +++ b/layout/src/topo/placer/place.rs @@ -66,5 +66,6 @@ impl<'a> Placer<'a> { if need_transpose { self.vg.transpose(); } + simple::adjust_subgraph_borders(self.vg); } } diff --git a/layout/src/topo/placer/simple.rs b/layout/src/topo/placer/simple.rs index fab902d..935c445 100644 --- a/layout/src/topo/placer/simple.rs +++ b/layout/src/topo/placer/simple.rs @@ -2,7 +2,9 @@ //! other. use super::EPSILON; +use crate::adt::dag::{NodeHandle, SubgraphHandle}; use crate::core::geometry::Point; +use crate::std_shapes::shapes::BORDER_PADDING; use crate::topo::layout::VisualGraph; /// Move the whole graph all the way to the left. @@ -22,11 +24,20 @@ pub(crate) fn align_to_left(vg: &mut VisualGraph) { } /// Assign the initial Y coordinates. -fn assign_y_coordinates(vg: &mut VisualGraph) { +pub fn assign_y_coordinates(vg: &mut VisualGraph) { let mut lowest_point = 0.; for i in 0..vg.dag.num_levels() { + // set the x of subgraphs in start_rank + for b_row in vg.dag.top_border_ranks()[i].clone().iter() { + let mut max_height_border: f64 = 0.; + for sg in b_row.iter() { + vg.pos_sg_mut(*sg).set_min_y(lowest_point); + max_height_border = + max_height_border.max(vg.size_sg_label(*sg).y); + } + lowest_point += max_height_border + BORDER_PADDING; + } let current_row = vg.dag.row(i); - // Find the tallest box in the row. let mut max_height: f64 = 0.; for idx in current_row.iter() { @@ -42,6 +53,53 @@ fn assign_y_coordinates(vg: &mut VisualGraph) { } lowest_point += max_height; + + for b_row in vg.dag.bottom_border_ranks()[i].clone().iter().rev() { + lowest_point += BORDER_PADDING; + for sg in b_row.iter() { + vg.pos_sg_mut(*sg).set_max_y(lowest_point); + } + lowest_point += BORDER_PADDING; + } + } +} + +pub fn adjust_subgraph_borders(vg: &mut VisualGraph) { + // TODO: we need to go through every non border node and adjust the borders + // since sometimes borders puts larger limit than needed to contain child nodes. + // Is there a more efficient way to do this? + + let subgraphs_num = vg.dag.num_subgraphs(); + let mut min_subgraph_x: Vec = vec![f64::MAX; subgraphs_num]; + let mut max_subgraph_x: Vec = vec![f64::MIN; subgraphs_num]; + for i in 0..vg.dag.len() { + let node = NodeHandle::new(i); + if vg.dag.is_vertical_border(node) { + break; + } + let sg = vg.dag.get_parent_subgraph_index_n(node); + let bbox = vg.pos(node).bbox(false); + min_subgraph_x[sg.get_index()] = + min_subgraph_x[sg.get_index()].min(bbox.0.x - BORDER_PADDING); + max_subgraph_x[sg.get_index()] = + max_subgraph_x[sg.get_index()].max(bbox.1.x + BORDER_PADDING); + } + + for i in (0..subgraphs_num).rev() { + for j in vg.dag.get_children_subgraphs(SubgraphHandle::new(i)).iter() { + let child_index = j.get_index(); + min_subgraph_x[i] = min_subgraph_x[i] + .min(min_subgraph_x[child_index] - BORDER_PADDING); + max_subgraph_x[i] = max_subgraph_x[i] + .max(max_subgraph_x[child_index] + BORDER_PADDING); + } + } + + for i in 0..subgraphs_num { + vg.pos_sg_mut(SubgraphHandle::new(i)) + .set_min_x(min_subgraph_x[i]); + vg.pos_sg_mut(SubgraphHandle::new(i)) + .set_max_x(max_subgraph_x[i]); } } diff --git a/layout/src/topo/sander.rs b/layout/src/topo/sander.rs new file mode 100644 index 0000000..5489342 --- /dev/null +++ b/layout/src/topo/sander.rs @@ -0,0 +1,82 @@ +use crate::topo::layout::VisualGraph; + +// Name tree refers the nested hierarchy of subgraphs and when ordering nodes with functions that has word tree +// this tree is taken into accoutn for detaills see the paper by sander + +fn update_col_map( + vg: &VisualGraph, + col_map: &mut Vec, + layer_idx: usize, +) { + for (j, n) in vg.dag.ranks()[layer_idx].iter().enumerate() { + col_map[n.get_index()] = j; + } +} + +pub(crate) fn do_it_inner(vg: &mut VisualGraph, col_map: &mut Vec) { + // print all nodes with their names + let layers = vg.dag.num_levels(); + vg.dag.subgraph_order_by_p_layerwise(0); + update_col_map(vg, col_map, 0); + // update col_map using last row + for i in 1..layers { + vg.dag.node_order_wp_layerwise(i, col_map); + vg.dag.subgraph_order_by_p_layerwise(i); + update_col_map(vg, col_map, i); + } + let lambda_order = vg.dag.subgraph_order_by_p(); + + // clear each layer and refill it according to lambda order + for layer_idx in 0..layers { + vg.dag.ranks_mut()[layer_idx].clear(); + } + for n in lambda_order { + let lvl = vg.dag.level(n); + vg.dag.ranks_mut()[lvl].push(n); + } + vg.dag.subgraph_order_by_p_layerwise(layers - 1); + update_col_map(vg, col_map, layers - 1); + for i in (0..layers - 1).rev() { + vg.dag.node_order_ws_layerwise(i, col_map); + vg.dag.subgraph_order_by_p_layerwise(i); + update_col_map(vg, col_map, i); + } + + let lambda_order = vg.dag.subgraph_order_by_p(); + + // clear each layer and refill it according to lambda order + for layer_idx in 0..layers { + vg.dag.ranks_mut()[layer_idx].clear(); + } + for n in lambda_order { + let lvl = vg.dag.level(n); + vg.dag.ranks_mut()[lvl].push(n); + } +} + +pub fn prepare_borders(vg: &mut VisualGraph) { + let subgraph_levels = vg.dag.get_subgraph_levels(); + vg.dag.place_horizontal_borders(&subgraph_levels); + vg.dag.place_vertical_borders(); + // insert Placeholder nodes for borders + for s in 0..vg.dag.num_subgraphs() { + let (lvl_start, lvl_end) = subgraph_levels[s]; + vg.add_vertical_border(lvl_start, lvl_end); + } +} + +const SANDERS_ITERATIONS: usize = 4; + +#[cfg_attr(not(feature = "log"), allow(unused_assignments, unused_variables))] +pub(crate) fn do_it(vg: &mut VisualGraph) { + let mut column_map = vec![0; vg.dag.len()]; + for r in vg.dag.ranks().iter() { + for (j, n) in r.iter().enumerate() { + column_map[n.get_index()] = j; + } + } + for _ in 0..SANDERS_ITERATIONS { + do_it_inner(vg, &mut column_map); + } + prepare_borders(vg); +}