using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Msagl.Core;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Geometry.Curves;
using Microsoft.Msagl.Core.Layout;
using Microsoft.Msagl.Layout.Incremental;
using Microsoft.Msagl.Layout.Layered;
using Microsoft.Msagl.Layout.MDS;
//using Microsoft.Msagl.Routing;
namespace Microsoft.Msagl.Layout.Initial
{
///
/// Methods for obtaining an initial layout of a graph using various means.
///
public class InitialLayout : AlgorithmBase
{
private GeometryGraph graph;
private FastIncrementalLayoutSettings settings;
private int componentCount;
///
/// Set to true if the graph specified is a single connected component with no clusters
///
public bool SingleComponent { get; set; }
///
/// Static layout of graph by gradually adding constraints.
/// Uses PivotMds to find initial layout.
/// Breaks the graph into connected components (nodes in the same cluster are considered
/// connected whether or not there is an edge between them), then lays out each component
/// individually. Finally, a simple packing is applied.
/// ratio as close as possible to the PackingAspectRatio property (not currently used).
///
public InitialLayout(GeometryGraph graph, FastIncrementalLayoutSettings settings)
{
ValidateArg.IsNotNull(graph, "graph");
ValidateArg.IsNotNull(settings, "settings");
this.graph = graph;
this.settings = new FastIncrementalLayoutSettings(settings);
this.settings.ApplyForces = true;
this.settings.InterComponentForces = true;
this.settings.RungeKuttaIntegration = false;
this.settings.RespectEdgePorts = false;
}
///
/// The actual layout process
///
protected override void RunInternal()
{
if (SingleComponent)
{
componentCount = 1;
LayoutComponent(graph);
}
else
{
foreach (var c in graph.RootCluster.AllClustersDepthFirst())
{
if (c == graph.RootCluster || c.RectangularBoundary == null) continue;
c.RectangularBoundary.GenerateFixedConstraints = false;
}
var components = graph.GetClusteredConnectedComponents().ToList();
componentCount = components.Count;
foreach (var component in components)
{
LayoutComponent(component);
}
graph.BoundingBox = MdsGraphLayout.PackGraphs(components, settings);
this.ProgressComplete();
// update positions of original graph elements
foreach (var v in graph.Nodes)
{
var copy = v.AlgorithmData as GraphConnectedComponents.AlgorithmDataNodeWrap;
Debug.Assert(copy != null);
v.Center = copy.node.Center;
}
foreach (var e in graph.Edges)
{
var copy = e.AlgorithmData as Edge;
if (copy != null)
{
e.EdgeGeometry = copy.EdgeGeometry;
e.EdgeGeometry.Curve = copy.Curve;
}
}
foreach (var c in graph.RootCluster.AllClustersDepthFirst().Where(c => c != graph.RootCluster))
{
var copy = c.AlgorithmData as GraphConnectedComponents.AlgorithmDataNodeWrap;
var copyCluster = copy.node as Cluster;
Debug.Assert(copyCluster != null);
c.RectangularBoundary = copyCluster.RectangularBoundary;
c.RectangularBoundary.GenerateFixedConstraints = c.RectangularBoundary.GenerateFixedConstraintsDefault;
c.BoundingBox = c.RectangularBoundary.Rect;
c.RaiseLayoutDoneEvent();
}
}
}
private void LayoutComponent(GeometryGraph component)
{
if (component.Nodes.Count > 1 || component.RootCluster.Clusters.Any())
{
// for small graphs (below 100 nodes) do extra iterations
settings.MaxIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation(
component.Nodes.Count,
/*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 5, /*maxIterations:*/ 10);
settings.MinorIterations = LayoutAlgorithmHelpers.NegativeLinearInterpolation(component.Nodes.Count,
/*lowerThreshold:*/ 50, /*upperThreshold:*/ 500, /*minIterations:*/ 3, /*maxIterations:*/ 20);
if (settings.MinConstraintLevel == 0)
{
// run PivotMDS with a largish Scale so that the layout comes back oversized.
// subsequent incremental iterations do a better job of untangling when they're pulling it in
// rather than pushing it apart.
PivotMDS pivotMDS = new PivotMDS(component) { Scale = 2 };
this.RunChildAlgorithm(pivotMDS, 0.5 / componentCount);
}
FastIncrementalLayout fil = new FastIncrementalLayout(component, settings, settings.MinConstraintLevel, anyCluster => settings);
Debug.Assert(settings.Iterations == 0);
foreach (var level in GetConstraintLevels(component))
{
if (level > settings.MaxConstraintLevel)
{
break;
}
if (level > settings.MinConstraintLevel)
{
fil.CurrentConstraintLevel = level;
}
do
{
fil.Run();
} while (!settings.IsDone);
}
}
// Pad the graph with margins so the packing will be spaced out.
component.Margins = settings.NodeSeparation;
component.UpdateBoundingBox();
// Zero the graph
component.Translate(-component.BoundingBox.LeftBottom);
}
///
/// Get the distinct ConstraintLevels that need to be applied to layout.
/// Used by InitialLayout.
/// Will only include ConstraintLevel == 1 if there are structural constraints
/// Will only include ConstraintLevel == 2 if AvoidOverlaps is on and there are fewer than 2000 nodes
///
/// 0, 1 or 2
IEnumerable GetConstraintLevels(GeometryGraph component)
{
var keys = (from c in this.settings.StructuralConstraints select c.Level).ToList();
keys.Add(0);
if (this.settings.IdealEdgeLength.EdgeDirectionConstraints != Directions.None) {
keys.Add(1);
}
if (this.settings.AvoidOverlaps && component.Nodes.Count < 2000) { keys.Add(2); }
return keys.Distinct();
}
}
}