Aetheria/Assets/Scripts/NIH/MSAGL/Layout/Layered/RecoveryLayeredLayoutEngine.cs
hyperion51 01a956eef6 MSAGL Support
Added new tech tree layout implementation using Microsoft Automatic Graph Layouts
Added some functionality to the database editor tools
2020-04-14 00:42:04 +01:00

1281 lines
54 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.Msagl.Core.DataStructures;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Geometry.Curves;
using Microsoft.Msagl.Core.GraphAlgorithms;
using Microsoft.Msagl.Core.Layout;
namespace Microsoft.Msagl.Layout.Layered {
/// <summary>
/// This is almost a copy, considering the data, of LayeredLayoutEngine.
/// The class restores itself from a given GeometryGraph
/// </summary>
internal class RecoveryLayeredLayoutEngine {
bool Brandes { get; set; }
LayerArrays engineLayerArrays;
GeometryGraph originalGraph;
ProperLayeredGraph properLayeredGraph;
/// <summary>
/// the sugiyama layout settings responsible for the algorithm parameters
/// </summary>
internal SugiyamaLayoutSettings sugiyamaSettings;
/// <summary>
/// the width of dummy nodes
/// </summary>
static double VirtualNodeWidth {
get { return 1; }
}
/// <summary>
/// the height of dummy nodes
/// </summary>
static double VirtualNodeHeight(SugiyamaLayoutSettings settings) {
return settings.MinNodeHeight * 1.5f / 8;
}
Database database;
/// <summary>
/// Keeps assorted data associated with the graph.
/// </summary>
internal Database Database {
set { database = value; }
get { return database; }
}
internal BasicGraph<Node, IntEdge> IntGraph; //the input graph
BasicGraph<Node, IntEdge> GluedDagSkeletonForLayering { get; set; }
//the graph obtained after X coord calculation
XLayoutGraph xLayoutGraph;
Dictionary<Node, int> nodeIdToIndex;
Anchor[] anchors;
readonly LayerArrays recoveredLayerArrays;
double[] layerToRecoveredYCoordinates;
readonly Dictionary<int, double> skeletonVirtualVerticesToX = new Dictionary<int, double>();
internal RecoveryLayeredLayoutEngine(GeometryGraph originalGraph) {
if (originalGraph != null) {
Init(originalGraph);
recoveredLayerArrays = RecoverOriginalLayersAndSettings(originalGraph, out sugiyamaSettings);
}
}
void FillLayersToRecoveredYCoordinates() {
layerToRecoveredYCoordinates = new double[recoveredLayerArrays.Layers.Length];
for (int i = 0; i < layerToRecoveredYCoordinates.Length; i++)
layerToRecoveredYCoordinates[i] = originalGraph.Nodes[recoveredLayerArrays.Layers[i][0]].Center.Y;
}
void Init(GeometryGraph geometryGraph) {
nodeIdToIndex = new Dictionary<Node, int>();
IList<Node> nodes = geometryGraph.Nodes;
InitNodesToIndex(nodes);
IntEdge[] intEdges = CreateIntEdges(geometryGraph);
IntGraph = new BasicGraph<Node, IntEdge>(intEdges, geometryGraph.Nodes.Count) { Nodes = nodes };
originalGraph = geometryGraph;
if (sugiyamaSettings == null)
sugiyamaSettings = new SugiyamaLayoutSettings();
CreateDatabaseAndRegisterIntEdgesInMultiEdges();
}
void CreateDatabaseAndRegisterIntEdgesInMultiEdges() {
Database = new Database();
foreach (IntEdge e in IntGraph.Edges)
database.RegisterOriginalEdgeInMultiedges(e);
}
IntEdge[] CreateIntEdges(GeometryGraph geometryGraph) {
var edges = geometryGraph.Edges;
var intEdges = new IntEdge[edges.Count];
int i = 0;
foreach (var edge in edges) {
if (edge.Source == null || edge.Target == null)
throw new InvalidOperationException(); //"creating an edge with null source or target");
var intEdge = new IntEdge(nodeIdToIndex[edge.Source], nodeIdToIndex[edge.Target], edge);
intEdges[i] = intEdge;
i++;
}
return intEdges;
}
void InitNodesToIndex(IList<Node> nodes) {
int index = 0;
foreach (Node n in nodes) {
nodeIdToIndex[n] = index;
index++;
}
}
internal static int EdgeSpan(int[] layers, IntEdge e) {
return layers[e.Source] - layers[e.Target];
}
/// <summary>
/// Creating a DAG which glues same layer vertices into one original,
/// replacing original multiple edges with a single edge.
/// upDown constraints will be added as edges
/// </summary>
void CreateGluedDagSkeletonForLayering() {
GluedDagSkeletonForLayering = new BasicGraph<Node, IntEdge>(GluedDagSkeletonEdges(),
originalGraph.Nodes.Count);
SetGluedEdgesWeights();
}
void SetGluedEdgesWeights() {
var gluedPairsToGluedEdge = new Dictionary<IntPair, IntEdge>();
foreach (IntEdge ie in GluedDagSkeletonForLayering.Edges)
gluedPairsToGluedEdge[new IntPair(ie.Source, ie.Target)] = ie;
foreach (var t in database.Multiedges)
if (t.Key.x != t.Key.y) {
IntPair gluedPair = VerticalConstraints.GluedIntPair(t.Key);
if (gluedPair.x == gluedPair.y) continue;
IntEdge gluedIntEdge = gluedPairsToGluedEdge[gluedPair];
foreach (IntEdge ie in t.Value)
gluedIntEdge.Weight += ie.Weight;
}
}
IEnumerable<IntEdge> GluedDagSkeletonEdges() {
var ret =
new Set<IntEdge>(from kv in database.Multiedges
where kv.Key.x != kv.Key.y
let e = VerticalConstraints.GluedIntEdge(kv.Value[0])
where e.Source != e.Target
select e);
IEnumerable<IntEdge> gluedUpDownConstraints = from p in VerticalConstraints.GluedUpDownIntConstraints
select CreateUpDownConstrainedIntEdge(p);
foreach (IntEdge edge in gluedUpDownConstraints)
ret.Insert(edge);
return ret;
}
static IntEdge CreateUpDownConstrainedIntEdge(IntPair intPair) {
var intEdge = new IntEdge(intPair.x, intPair.y);
intEdge.Weight = 0;
//we do not want the edge weight to contribute in to the sum but just take the constraint into account
intEdge.Separation = 1;
return intEdge;
}
/// <summary>
/// The main calculation is done here
/// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider",
MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)"),
SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider",
MessageId = "System.String.Format(System.String,System.Object)")]
internal void Run() {
if (originalGraph.Nodes.Count > 0) {
engineLayerArrays = CalculateLayers();
} else
originalGraph.boundingBox.SetToEmpty();
}
// private void CheckLayer(int[]l) {
// ///debug
//
// for (int i = 0; i < l.Length - 1; i++)
// if (NodesAreTooClose(l[i], l[i + 1]))
// Console.WriteLine("nodes are too close");
// }
//
// private bool NodesAreTooClose (int u, int v) {
// Anchor a = dataBase.Anchors[u], side1 = dataBase.Anchors[v];
// return a.Right + originalGraph.NodeSeparation > side1.Left;
// }
LayerArrays CalculateLayers() {
CreateGluedDagSkeletonForLayering();
LayerArrays layerArrays = CalculateLayersWithoutAspectRatio();
UpdateNodePositionData();
return layerArrays;
}
/// <summary>
/// pushes positions from the anchors to node Centers
/// </summary>
void UpdateNodePositionData() {
TryToSatisfyMinWidhtAndMinHeight();
for (int i = 0; i < IntGraph.Nodes.Count && i < database.Anchors.Length; i++)
IntGraph.Nodes[i].Center = database.Anchors[i].Origin;
}
void TryToSatisfyMinWidhtAndMinHeight() {
TryToSatisfyMinWidth();
TryToSatisfyMinHeight();
}
void TryToSatisfyMinWidth() {
if (sugiyamaSettings.MinimalWidth == 0)
return;
double w = GetCurrentWidth();
if (w < sugiyamaSettings.MinimalWidth)
StretchWidth();
}
/// <summary>
/// </summary>
void StretchWidth() {
//calculate the desired span of the anchor centers and the current span of anchor center
var desiredSpan = new RealNumberSpan();
foreach (Node node in originalGraph.Nodes) {
desiredSpan.AddValue(node.BoundingBox.Width / 2);
desiredSpan.AddValue(sugiyamaSettings.MinimalWidth - node.BoundingBox.Width / 2);
}
var currentSpan = new RealNumberSpan();
foreach (Anchor anchor in NodeAnchors())
currentSpan.AddValue(anchor.X);
if (currentSpan.Length > ApproximateComparer.DistanceEpsilon) {
double stretch = desiredSpan.Length / currentSpan.Length;
if (stretch > 1)
foreach (Anchor a in anchors)
a.X *= stretch;
}
}
void TryToSatisfyMinHeight() {
if (sugiyamaSettings.MinimalHeight == 0)
return;
double h = GetCurrentHeight();
if (h < sugiyamaSettings.MinimalHeight)
StretchHeight();
}
double GetCurrentHeight() {
var span = new RealNumberSpan();
foreach (Anchor anchor in NodeAnchors()) {
span.AddValue(anchor.Top);
span.AddValue(anchor.Bottom);
}
return span.Length;
}
void StretchHeight() {
var desiredSpan = new RealNumberSpan();
foreach (Node node in originalGraph.Nodes) {
desiredSpan.AddValue(node.BoundingBox.Height / 2);
desiredSpan.AddValue(sugiyamaSettings.MinimalHeight - node.BoundingBox.Height / 2);
}
var currentSpan = new RealNumberSpan();
foreach (Anchor anchor in NodeAnchors())
currentSpan.AddValue(anchor.Y);
if (currentSpan.Length > ApproximateComparer.DistanceEpsilon) {
double stretch = desiredSpan.Length / currentSpan.Length;
if (stretch > 1)
foreach (Anchor a in anchors)
a.Y *= stretch;
}
}
IEnumerable<Anchor> NodeAnchors() {
return anchors.Take(Math.Min(IntGraph.Nodes.Count, anchors.Length));
}
double GetCurrentWidth() {
var span = new RealNumberSpan();
foreach (Anchor anchor in NodeAnchors()) {
span.AddValue(anchor.Left);
span.AddValue(anchor.Right);
}
return span.Length;
}
void StretchToDesiredAspectRatio(double aspectRatio, double desiredAr) {
if (aspectRatio > desiredAr)
StretchInYDirection(aspectRatio / desiredAr);
else if (aspectRatio < desiredAr)
StretchInXDirection(desiredAr / aspectRatio);
}
void StretchInYDirection(double scaleFactor) {
double center = (originalGraph.BoundingBox.Top + originalGraph.BoundingBox.Bottom) / 2;
foreach (Anchor a in Database.Anchors) {
a.BottomAnchor *= scaleFactor;
a.TopAnchor *= scaleFactor;
a.Y = center + scaleFactor * (a.Y - center);
}
double h = originalGraph.Height * scaleFactor;
originalGraph.BoundingBox = new Rectangle(originalGraph.BoundingBox.Left, center + h / 2,
originalGraph.BoundingBox.Right, center - h / 2);
}
void StretchInXDirection(double scaleFactor) {
double center = (originalGraph.BoundingBox.Left + originalGraph.BoundingBox.Right) / 2;
foreach (Anchor a in Database.Anchors) {
a.LeftAnchor *= scaleFactor;
a.RightAnchor *= scaleFactor;
a.X = center + scaleFactor * (a.X - center);
}
double w = originalGraph.Width * scaleFactor;
originalGraph.BoundingBox = new Rectangle(center - w / 2, originalGraph.BoundingBox.Top, center + w / 2,
originalGraph.BoundingBox.Bottom);
}
LayerArrays CalculateLayersWithoutAspectRatio() {
LayerArrays layerArrays = CalculateYLayers();
if (constrainedOrdering == null) {
DecideIfUsingFastXCoordCalculation(layerArrays);
CalculateAnchorsAndYPositions(layerArrays);
if (Brandes)
CalculateXPositionsByBrandes(layerArrays);
else
CalculateXLayersByGansnerNorth(layerArrays);
} else
anchors = database.Anchors;
OptimizeEdgeLabelsLocations();
engineLayerArrays = layerArrays;
StraightensShortEdges();
double aspectRatio;
CalculateOriginalGraphBox(out aspectRatio);
if (sugiyamaSettings.AspectRatio != 0)
StretchToDesiredAspectRatio(aspectRatio, sugiyamaSettings.AspectRatio);
return layerArrays;
}
/*
void CalculateXPositionsWithQP(LayerArrays layerArrays){
ISolverShell solver = ConstrainedOrdering.CreateSolver();
foreach (var layer in layerArrays.Layers)
AddNotOverlappingConstraints(layer, solver);
foreach (var list in database.Multiedges.Values)
foreach (IntEdge edge in list)
AddEdgeToSolverTarget(edge, solver, edge.Weight);
int j = -1;
for (int i = 0; i < intGraph.Nodes.Count; i++)
if(intGraph.OutEdgesCount(i) != 0 || intGraph.InEdgesCount(i) != 0){
j = i;
break;
}
if(j != -1){
for (int i = 0; i < intGraph.Nodes.Count; i++)
if(intGraph.OutEdgesCount(i) == 0 && intGraph.InEdgesCount(i) == 0)
solver.AddGoalTwoVariablesAreClose(i, j);
}
j = 0;
foreach (Anchor a in database.Anchors)
a.X = solver.GetVariableResolvedPosition(j++);
}
*/
void DecideIfUsingFastXCoordCalculation(LayerArrays layerArrays) {
if (layerArrays.X.Length >= sugiyamaSettings.BrandesThreshold)
Brandes = true;
else {
string s = Environment.GetEnvironmentVariable("Brandes");
if (!String.IsNullOrEmpty(s) && String.Compare(s, "on", true, CultureInfo.CurrentCulture) == 0)
Brandes = true;
}
}
void StraightensShortEdges() {
for (; StraightenEdgePaths(); ) { }
}
bool StraightenEdgePaths() {
bool ret = false;
foreach (IntEdge e in database.AllIntEdges)
if (e.LayerSpan == 2)
ret =
ShiftVertexWithNeighbors(e.LayerEdges[0].Source, e.LayerEdges[0].Target, e.LayerEdges[1].Target) ||
ret;
return ret;
//foreach (LayerEdge[][] edgeStrings in this.dataBase.RefinedEdges.Values)
// if (edgeStrings[0].Length == 2)
// foreach (LayerEdge[] edgePath in edgeStrings)
// ret = ShiftVertexWithNeighbors(edgePath[0].Source, edgePath[0].Target, edgePath[1].Target) || ret;
//return ret;
}
bool ShiftVertexWithNeighbors(int u, int i, int v) {
Anchor upper = database.Anchors[u];
Anchor lower = database.Anchors[v];
Anchor iAnchor = database.Anchors[i];
//calculate the ideal x position for i
// (x- upper.x)/(iAnchor.y-upper.y)=(lower.x-upper.x)/(lower.y-upper.y)
double x = (iAnchor.Y - upper.Y) * (lower.X - upper.X) / (lower.Y - upper.Y) + upper.X;
const double eps = 0.0001;
if (x > iAnchor.X + eps)
return TryShiftToTheRight(x, i);
if (x < iAnchor.X - eps)
return TryShiftToTheLeft(x, i);
return false;
}
bool TryShiftToTheLeft(double x, int v) {
int[] layer = engineLayerArrays.Layers[engineLayerArrays.Y[v]];
int vPosition = engineLayerArrays.X[v];
if (vPosition > 0) {
Anchor uAnchor = database.Anchors[layer[vPosition - 1]];
double allowedX = Math.Max(
uAnchor.Right + sugiyamaSettings.NodeSeparation + database.Anchors[v].LeftAnchor, x);
if (allowedX < database.Anchors[v].X - 1) {
database.Anchors[v].X = allowedX;
return true;
}
return false;
}
database.Anchors[v].X = x;
return true;
}
bool TryShiftToTheRight(double x, int v) {
int[] layer = engineLayerArrays.Layers[engineLayerArrays.Y[v]];
int vPosition = engineLayerArrays.X[v];
if (vPosition < layer.Length - 1) {
Anchor uAnchor = database.Anchors[layer[vPosition + 1]];
double allowedX = Math.Min(
uAnchor.Left - sugiyamaSettings.NodeSeparation - database.Anchors[v].RightAnchor, x);
if (allowedX > database.Anchors[v].X + 1) {
database.Anchors[v].X = allowedX;
return true;
}
return false;
}
database.Anchors[v].X = x;
return true;
}
LayerArrays CalculateYLayers() {
LayerArrays layerArrays =
YLayeringAndOrdering(new RecoveryLayerCalculator(recoveredLayerArrays));
if (constrainedOrdering != null)
return layerArrays;
return InsertLayersIfNeeded(layerArrays);
}
VerticalConstraintsForSugiyama VerticalConstraints {
get { return sugiyamaSettings.VerticalConstraints; }
}
HorizontalConstraintsForSugiyama HorizontalConstraints {
get { return sugiyamaSettings.HorizontalConstraints; }
}
void CalculateAnchorsAndYPositions(LayerArrays layerArrays) {
CalculateAnchorSizes(database, out anchors, properLayeredGraph, originalGraph, IntGraph, sugiyamaSettings);
CalcInitialYAnchorLocations(layerArrays, 500, originalGraph, database, IntGraph, sugiyamaSettings,
LayersAreDoubled);
}
/// <summary>
/// put some labels to the left of the splines if it makes sense
/// </summary>
void OptimizeEdgeLabelsLocations() {
for (int i = 0; i < anchors.Length; i++) {
Anchor a = anchors[i];
if (a.LabelToTheRightOfAnchorCenter) {
//by default the label is put to the right of the spline
Anchor predecessor;
Anchor successor;
GetSuccessorAndPredecessor(i, out predecessor, out successor);
if (!TryToPutLabelOutsideOfAngle(a, predecessor, successor)) {
double sumNow = (predecessor.Origin - a.Origin).Length + (successor.Origin - a.Origin).Length;
double nx = a.Right - a.LeftAnchor; //new potential anchor center
var xy = new Point(nx, a.Y);
double sumWouldBe = (predecessor.Origin - xy).Length + (successor.Origin - xy).Length;
if (sumWouldBe < sumNow) //we need to swap
PutLabelToTheLeft(a);
}
}
}
}
static bool TryToPutLabelOutsideOfAngle(Anchor a, Anchor predecessor, Anchor successor) {
if (a.LabelToTheRightOfAnchorCenter) {
if (Point.GetTriangleOrientation(predecessor.Origin, a.Origin, successor.Origin) ==
TriangleOrientation.Clockwise)
return true;
double la = a.LeftAnchor;
double ra = a.RightAnchor;
double x = a.X;
PutLabelToTheLeft(a);
if (Point.GetTriangleOrientation(predecessor.Origin, a.Origin, successor.Origin) ==
TriangleOrientation.Counterclockwise)
return true;
a.X = x;
a.LeftAnchor = la;
a.RightAnchor = ra;
a.LabelToTheRightOfAnchorCenter = true;
a.LabelToTheLeftOfAnchorCenter = false;
return false;
}
return false;
}
static void PutLabelToTheLeft(Anchor a) {
double r = a.Right;
double t = a.LeftAnchor;
a.LeftAnchor = a.RightAnchor;
a.RightAnchor = t;
a.X = r - a.RightAnchor;
a.LabelToTheLeftOfAnchorCenter = true;
a.LabelToTheRightOfAnchorCenter = false;
}
void GetSuccessorAndPredecessor(int i, out Anchor p, out Anchor s) {
int predecessor = 10; //the value does not matter, just to silence the compiler
foreach (LayerEdge ie in properLayeredGraph.InEdges(i))
predecessor = ie.Source; // there will be only one
int successor = 10; // the value does not matter, just to silence the compiler
foreach (LayerEdge ie in properLayeredGraph.OutEdges(i))
successor = ie.Target; //there will be only one
//we compare the sum of length of projections of edges (predecessor,i), (i,successor) to x in cases when the label is to the right and to the left
p = anchors[predecessor];
s = anchors[successor];
}
//void MakeXOfAnchorsPositive(LayerArrays layerArrays)
//{
// //find the minimum of x of anchors
// //we don't care about y since they are not part of the unknown variables
// double min;
// if (anchors.Length > 0)
// {
// min = anchors[0].X;
// for (int i = 1; i < anchors.Length; i++)
// {
// double x = anchors[i].X;
// if (x < min)
// min = x;
// }
// //span of the layers in Y direction
// int[][] layers = layerArrays.Layers;
// double s = anchors[layers[layers.Length - 1][0]].Y - anchors[layers[0][0]].Y;
// //shift anchors to the right that their minimum would be equal to min, unless min is already not less than s.
// if (min < s)
// {
// double shift = s - min;
// for (int i = 0; i < anchors.Length; i++)
// anchors[i].X += shift;
// }
// }
//}
/// <summary>
/// Create a DAG from the given graph
/// </summary>
void CycleRemoval() {
var feedbackSet = IntGraph.Edges.Where(e => recoveredLayerArrays.Y[e.Source] < recoveredLayerArrays.Y[e.Target]).ToArray();
database.AddFeedbackSet(feedbackSet);
}
/// <summary>
/// The function calculates y-layers and x-layers,
/// thus, in fact, defining node, including dummy nodes, locations.
/// </summary>
/// <param name="layerArrays"></param>
void CalculateXLayersByGansnerNorth(LayerArrays layerArrays) {
xLayoutGraph = CreateXLayoutGraph(layerArrays);
CalculateXLayersByGansnerNorthOnProperLayeredGraph();
}
void CalculateXLayersByGansnerNorthOnProperLayeredGraph() {
int[] xLayers = (new NetworkSimplex(xLayoutGraph, null)).GetLayers();
//TestYXLayers(layerArrays, xLayers);//this will not be called in the release version
for (int i = 0; i < database.Anchors.Length; i++)
anchors[i].X = xLayers[i];
}
//[System.Diagnostics.Conditional("DEBUGGLEE")]
//private void TestYXLayers(LayerArrays layerArrays, int[] xLayers) {
// foreach (IntEdge e in this.xLayoutGraph.Edges) {
// int s = e.Source; int targ = e.Target;
// if (e.Source >= layeredGraph.Nodes.Count) {
// if (xLayoutGraph.OutEdges(s).Count != 2 || xLayoutGraph.InEdges(s).Count != 0)
// Report("must be two out edges and none incoming");
// if (targ >= layeredGraph.Nodes.Count)
// Report("an illegal edge");
// } else {
// if (layerArrays.Y[s] != layerArrays.Y[targ])
// Report("layers don't coincide");
// if (layerArrays.X[s] - 1 != layerArrays.X[targ])
// Report("wrong input");
// if (xLayers[s] <= xLayers[targ])
// Report("wrong xlayering");
// }
// }
//}
/// <summary>
/// Creating a proper layered graph, a graph where each
/// edge goes only one layer down from the i+1-th layer to the i-th layer.
/// </summary>
/// <param name="layering"></param>
/// <param name="layerArrays"></param>
void CreateProperLayeredGraph(int[] layering, out LayerArrays layerArrays) {
int n = layering.Length;
int nOfVv = 0;
foreach (IntEdge e in database.SkeletonEdges()) {
int span = EdgeSpan(layering, e);
Debug.Assert(span >= 0);
if (span > 0)
e.LayerEdges = new LayerEdge[span];
int pe = 0; //offset in the string
if (span > 1) {
//we create span-2 dummy nodes and span new edges
int d0 = n + nOfVv++;
var layerEdge = new LayerEdge(e.Source, d0, e.CrossingWeight);
e.LayerEdges[pe++] = layerEdge;
//create span-2 internal edges all from dummy nodes
for (int j = 0; j < span - 2; j++) {
d0++;
nOfVv++;
layerEdge = new LayerEdge(d0 - 1, d0, e.CrossingWeight);
e.LayerEdges[pe++] = layerEdge;
}
layerEdge = new LayerEdge(d0, e.Target, e.CrossingWeight);
e.LayerEdges[pe] = layerEdge;
} else if (span == 1) {
var layerEdge = new LayerEdge(e.Source, e.Target, e.CrossingWeight);
e.LayerEdges[pe] = layerEdge;
}
}
var extendedVertexLayering = new int[originalGraph.Nodes.Count + nOfVv];
foreach (IntEdge e in database.SkeletonEdges())
if (e.LayerEdges != null) {
int l = layering[e.Source];
extendedVertexLayering[e.Source] = l--;
foreach (LayerEdge le in e.LayerEdges)
extendedVertexLayering[le.Target] = l--;
} else {
extendedVertexLayering[e.Source] = layering[e.Source];
extendedVertexLayering[e.Target] = layering[e.Target];
}
properLayeredGraph =
new ProperLayeredGraph(new BasicGraph<Node, IntEdge>(database.SkeletonEdges(), layering.Length));
properLayeredGraph.BaseGraph.Nodes = IntGraph.Nodes;
layerArrays = new LayerArrays(extendedVertexLayering);
}
ConstrainedOrdering constrainedOrdering;
LayerArrays YLayeringAndOrdering(LayerCalculator layering) {
int[] yLayers = layering.GetLayers();
yLayers = ExtendLayeringToUngluedSameLayerVertices(yLayers);
var layerArrays = new LayerArrays(yLayers);
//if (!SugiyamaSettings.UseEdgeBundling && (HorizontalConstraints == null || HorizontalConstraints.IsEmpty)) {
if (HorizontalConstraints == null || HorizontalConstraints.IsEmpty) {
layerArrays = YLayeringAndOrderingWithoutHorizontalConstraints(layerArrays);
return layerArrays;
}
constrainedOrdering = new ConstrainedOrdering(originalGraph, IntGraph, layerArrays.Y, nodeIdToIndex,
database, sugiyamaSettings);
constrainedOrdering.Calculate();
properLayeredGraph = constrainedOrdering.ProperLayeredGraph;
// SugiyamaLayoutSettings.ShowDatabase(this.database);
return constrainedOrdering.LayerArrays;
}
LayerArrays YLayeringAndOrderingWithoutHorizontalConstraints(LayerArrays layerArrays) {
CreateProperLayeredGraph(layerArrays.Y, out layerArrays);
GetXCoordinatesOfVirtualNodesOfTheProperLayeredGraph(layerArrays);
OrderLayers(layerArrays);
MetroMapOrdering.UpdateLayerArrays(properLayeredGraph, layerArrays);
return layerArrays;
}
void GetXCoordinatesOfVirtualNodesOfTheProperLayeredGraph(LayerArrays layerArrays) {
foreach (var skeletonEdge in database.SkeletonEdges())
GetXCoordinatesOfVirtualNodesOfTheProperLayeredGraphForSkeletonEdge(skeletonEdge, layerArrays);
}
void GetXCoordinatesOfVirtualNodesOfTheProperLayeredGraphForSkeletonEdge(IntEdge intEdge, LayerArrays layerArrays) {
if (intEdge.LayerEdges == null || intEdge.LayerEdges.Count < 2) return;
var edgeCurve = intEdge.Edge.Curve;
var layerIndex = layerArrays.Y[intEdge.Source] - 1;//it is the layer of the highest virtual node of the edge
for (int i = 1; i < intEdge.LayerEdges.Count; i++) {
var v = intEdge.LayerEdges[i].Source;
var layerY = layerToRecoveredYCoordinates[layerIndex--];
var layerLine = new LineSegment(new Point(originalGraph.Left, layerY), new Point(originalGraph.Right, layerY));
var intersection=Curve.CurveCurveIntersectionOne(edgeCurve, layerLine, false);
skeletonVirtualVerticesToX[v] =intersection.IntersectionPoint.X;
}
}
void OrderLayers(LayerArrays layerArrays) {
foreach (var layer in layerArrays.Layers)
OrderLayerBasedOnRecoveredXCoords(layer);
}
void OrderLayerBasedOnRecoveredXCoords(int[] layer) {
if (layer.Length <= 1) return;
var keys = new double[layer.Length];
for (int i = 0; i < layer.Length; i++) {
int v = layer[i];
keys[i] = properLayeredGraph.IsVirtualNode(v)
? skeletonVirtualVerticesToX[v]
: originalGraph.Nodes[v].Center.X;
}
Array.Sort(keys, layer);
}
void CalculateXPositionsByBrandes(LayerArrays layerArrays) {
XCoordsWithAlignment.CalculateXCoordinates(layerArrays, properLayeredGraph, originalGraph.Nodes.Count,
database.Anchors, sugiyamaSettings.NodeSeparation);
}
void CalculateOriginalGraphBox(out double aspectRatio) {
aspectRatio = 0;
if (anchors.Length > 0) {
var box = new Rectangle(anchors[0].Left, anchors[0].Top, anchors[0].Right, anchors[0].Bottom);
for (int i = 1; i < anchors.Length; i++) {
Anchor a = anchors[i];
box.Add(a.LeftTop);
box.Add(a.RightBottom);
}
aspectRatio = box.Width / box.Height;
// Console.WriteLine("bb aspect ratio {0}", aspectRatio);
double delta = (box.LeftTop - box.RightBottom).Length / 2;
var del = new Point(-delta, delta);
box.Add(box.LeftTop + del);
box.Add(box.RightBottom - del);
originalGraph.BoundingBox = box;
}
}
LayerArrays InsertLayersIfNeeded(LayerArrays layerArrays) {
bool needToInsertLayers = false;
bool multipleEdges = false;
InsertVirtualEdgesIfNeeded(layerArrays);
AnalyzeNeedToInsertLayersAndHasMultiedges(layerArrays, ref needToInsertLayers, ref multipleEdges);
if (needToInsertLayers) {
LayerInserter.InsertLayers(ref properLayeredGraph, ref layerArrays, database, IntGraph);
LayersAreDoubled = true;
} else if (multipleEdges)
EdgePathsInserter.InsertPaths(ref properLayeredGraph, ref layerArrays, database, IntGraph);
RecreateIntGraphFromDataBase();
return layerArrays;
}
bool LayersAreDoubled { get; set; }
void RecreateIntGraphFromDataBase() {
var edges = new List<IntEdge>();
foreach (var list in database.Multiedges.Values)
edges.AddRange(list);
IntGraph.SetEdges(edges, IntGraph.NodeCount);
}
void AnalyzeNeedToInsertLayersAndHasMultiedges(LayerArrays layerArrays, ref bool needToInsertLayers,
ref bool multipleEdges) {
foreach (IntEdge ie in IntGraph.Edges)
if (ie.HasLabel && layerArrays.Y[ie.Source] != layerArrays.Y[ie.Target]) {
//if an edge is a flat edge then
needToInsertLayers = true;
break;
}
if (needToInsertLayers == false && constrainedOrdering == null)
//if we have constrains the multiple edges have been already represented in layers
foreach (var kv in database.Multiedges)
if (kv.Value.Count > 1) {
multipleEdges = true;
if (layerArrays.Y[kv.Key.x] - layerArrays.Y[kv.Key.y] == 1) {
//there is a multi edge spanning exactly one layer; unfortunately we need to introduce virtual vertices for
//the edges middle points
needToInsertLayers = true;
break;
}
}
}
void InsertVirtualEdgesIfNeeded(LayerArrays layerArrays) {
if (constrainedOrdering != null) //if there are constraints we handle multiedges correctly
return;
foreach (var kv in database.Multiedges)
// If there are an even number of multi-edges between two nodes then
// add a virtual edge in the multi-edge dict to improve the placement, but only in case when the edge goes down only one layer.
if (kv.Value.Count % 2 == 0 && layerArrays.Y[kv.Key.First] - 1 == layerArrays.Y[kv.Key.Second]) {
var newVirtualEdge = new IntEdge(kv.Key.First, kv.Key.Second);
newVirtualEdge.Edge = new Edge();
newVirtualEdge.IsVirtualEdge = true;
kv.Value.Insert(kv.Value.Count / 2, newVirtualEdge);
IntGraph.AddEdge(newVirtualEdge);
}
}
int[] ExtendLayeringToUngluedSameLayerVertices(int[] p) {
VerticalConstraintsForSugiyama vc = VerticalConstraints;
for (int i = 0; i < p.Length; i++)
p[i] = p[vc.NodeToRepr(i)];
return p;
}
internal static void CalculateAnchorSizes(Database database, out Anchor[] anchors,
ProperLayeredGraph properLayeredGraph, GeometryGraph originalGraph,
BasicGraph<Node, IntEdge> intGraph, SugiyamaLayoutSettings settings) {
database.Anchors = anchors = new Anchor[properLayeredGraph.NodeCount];
for (int i = 0; i < anchors.Length; i++)
anchors[i] = new Anchor(settings.LabelCornersPreserveCoefficient);
//go over the old vertices
for (int i = 0; i < originalGraph.Nodes.Count; i++)
CalcAnchorsForOriginalNode(i, intGraph, anchors, database, settings);
//go over virtual vertices
foreach (IntEdge intEdge in database.AllIntEdges)
if (intEdge.LayerEdges != null) {
foreach (LayerEdge layerEdge in intEdge.LayerEdges) {
int v = layerEdge.Target;
if (v != intEdge.Target) {
Anchor anchor = anchors[v];
if (!database.MultipleMiddles.Contains(v)) {
anchor.LeftAnchor = anchor.RightAnchor = VirtualNodeWidth / 2.0f;
anchor.TopAnchor = anchor.BottomAnchor = VirtualNodeHeight(settings) / 2.0f;
} else {
anchor.LeftAnchor = anchor.RightAnchor = VirtualNodeWidth * 4;
anchor.TopAnchor = anchor.BottomAnchor = VirtualNodeHeight(settings) / 2.0f;
}
}
}
//fix label vertices
if (intEdge.HasLabel) {
int lj = intEdge.LayerEdges[intEdge.LayerEdges.Count / 2].Source;
Anchor a = anchors[lj];
double w = intEdge.LabelWidth, h = intEdge.LabelHeight;
a.RightAnchor = w;
a.LeftAnchor = VirtualNodeWidth * 8;
if (a.TopAnchor < h / 2.0)
a.TopAnchor = a.BottomAnchor = h / 2.0;
a.LabelToTheRightOfAnchorCenter = true;
}
}
}
/// <summary>
///
/// </summary>
/// <returns>the height of the graph+spaceBeforeMargins</returns>
internal static void CalcInitialYAnchorLocations(LayerArrays layerArrays, double spaceBeforeMargins,
GeometryGraph originalGraph, Database database,
BasicGraph<IntEdge> intGraph,
SugiyamaLayoutSettings settings,
bool layersAreDoubled) {
Anchor[] anchors = database.Anchors;
double ymax = originalGraph.Margins + spaceBeforeMargins; //setting up y coord - going up by y-layers
int i = 0;
foreach (var yLayer in layerArrays.Layers) {
double bottomAnchorMax = 0;
double topAnchorMax = 0;
foreach (int j in yLayer) {
Anchor p = anchors[j];
if (p.BottomAnchor > bottomAnchorMax)
bottomAnchorMax = p.BottomAnchor;
if (p.TopAnchor > topAnchorMax)
topAnchorMax = p.TopAnchor;
}
MakeVirtualNodesHigh(yLayer, bottomAnchorMax, topAnchorMax, originalGraph.Nodes.Count, database.Anchors);
double flatEdgesHeight = SetFlatEdgesForLayer(database, layerArrays, i, intGraph, settings, ymax);
double y = ymax + bottomAnchorMax + flatEdgesHeight;
foreach (int j in yLayer)
anchors[j].Y = y;
double layerSep = settings.ActualLayerSeparation(layersAreDoubled);
ymax = y + topAnchorMax + layerSep;
i++;
}
SetFlatEdgesForLayer(database, layerArrays, i, intGraph, settings, ymax);
}
static double SetFlatEdgesForLayer(Database database, LayerArrays layerArrays, int i,
BasicGraph<IntEdge> intGraph, SugiyamaLayoutSettings settings, double ymax) {
double flatEdgesHeight = 0;
if (i > 0) {
//looking for flat edges on the previous level
//we stack labels of multiple flat edges on top of each other
IEnumerable<IntPair> flatPairs = GetFlatPairs(layerArrays.Layers[i - 1], layerArrays.Y,
intGraph);
if (flatPairs.Any()) {
double dyOfFlatEdge = settings.LayerSeparation / 3;
double ym = ymax;
flatEdgesHeight =
(from pair in flatPairs
select SetFlatEdgesLabelsHeightAndPositionts(pair, ym, dyOfFlatEdge, database)).
Max();
}
}
return flatEdgesHeight;
}
static double SetFlatEdgesLabelsHeightAndPositionts(IntPair pair, double ymax, double dy, Database database) {
double height = 0;
List<IntEdge> list = database.GetMultiedge(pair);
foreach (IntEdge edge in list) {
height += dy;
Label label = edge.Edge.Label;
if (label != null) {
label.Center = new Point(label.Center.X, ymax + height + label.Height / 2);
height += label.Height;
}
}
return height;
}
static IEnumerable<IntPair> GetFlatPairs(int[] layer, int[] layering, BasicGraph<IntEdge> intGraph) {
return new Set<IntPair>(from v in layer
where v < intGraph.NodeCount
from edge in intGraph.OutEdges(v)
where layering[edge.Source] == layering[edge.Target]
select new IntPair(edge.Source, edge.Target));
}
static void MakeVirtualNodesHigh(int[] yLayer, double bottomAnchorMax, double topAnchorMax,
int originalNodeCount, Anchor[] anchors) {
if (LayerIsOriginal(yLayer, originalNodeCount))
foreach (int j in yLayer)
if (j >= originalNodeCount) {
Anchor p = anchors[j];
p.BottomAnchor = bottomAnchorMax;
p.TopAnchor = topAnchorMax;
}
}
static bool LayerIsOriginal(int[] yLayer, int origNodeCount) {
foreach (int j in yLayer)
if (j < origNodeCount)
return true;
return false;
}
static void CalcAnchorsForOriginalNode(int i, BasicGraph<Node, IntEdge> intGraph, Anchor[] anchors,
Database database, SugiyamaLayoutSettings settings) {
double leftAnchor = 0;
double rightAnchor = leftAnchor;
double topAnchor = 0;
double bottomAnchor = topAnchor;
//that's what we would have without the label and multiedges
if (intGraph.Nodes != null) {
Node node = intGraph.Nodes[i];
ExtendStandardAnchors(ref leftAnchor, ref rightAnchor, ref topAnchor, ref bottomAnchor, node);
}
RightAnchorMultiSelfEdges(i, ref rightAnchor, ref topAnchor, ref bottomAnchor, database, settings);
double hw = settings.MinNodeWidth / 2;
if (leftAnchor < hw)
leftAnchor = hw;
if (rightAnchor < hw)
rightAnchor = hw;
double hh = settings.MinNodeHeight / 2;
if (topAnchor < hh)
topAnchor = hh;
if (bottomAnchor < hh)
bottomAnchor = hh;
anchors[i] = new Anchor(leftAnchor, rightAnchor, topAnchor, bottomAnchor, intGraph.Nodes[i],
settings.LabelCornersPreserveCoefficient) { Padding = intGraph.Nodes[i].Padding };
#if TEST_MSAGL
anchors[i].UserData = intGraph.Nodes[i].UserData;
#endif
}
static void RightAnchorMultiSelfEdges(int i, ref double rightAnchor, ref double topAnchor,
ref double bottomAnchor, Database database,
SugiyamaLayoutSettings settings) {
double delta = WidthOfSelfEdge(database, i, ref rightAnchor, ref topAnchor, ref bottomAnchor, settings);
rightAnchor += delta;
}
static double WidthOfSelfEdge(Database database, int i, ref double rightAnchor, ref double topAnchor,
ref double bottomAnchor, SugiyamaLayoutSettings settings) {
double delta = 0;
List<IntEdge> multiedges = database.GetMultiedge(i, i);
//it could be a multiple self edge
if (multiedges.Count > 0) {
foreach (IntEdge e in multiedges)
if (e.Edge.Label != null) {
rightAnchor += e.Edge.Label.Width;
if (topAnchor < e.Edge.Label.Height / 2.0)
topAnchor = bottomAnchor = e.Edge.Label.Height / 2.0f;
}
delta += (settings.NodeSeparation + settings.MinNodeWidth) * multiedges.Count;
}
return delta;
}
static void ExtendStandardAnchors(ref double leftAnchor, ref double rightAnchor, ref double topAnchor,
ref double bottomAnchor, Node node) {
double w = node.Width;
double h = node.Height;
w /= 2.0;
h /= 2.0;
rightAnchor = leftAnchor = w;
topAnchor = bottomAnchor = h;
}
///// <summary>
///// A quote from Gansner93.
///// The method involves constructing an auxiliary graph as illustrated in figure 4-2.
///// This transformation is the graphical analogue of the algebraic
///// transformation mentioned above for removing the absolute values
///// from the optimization problem. The nodes of the auxiliary graph Gў are the nodes of
///// the original graph G plus, for every edge e in G, there is a new node ne.
///// There are two kinds of edges in Gў. One edge class encodes the
///// cost of the original edges. Every edge e = (u,v) in G is replaced by two edges (ne ,u)
///// and (ne, v) with d = 0 and w = w(e)W(e). The other class of edges separates nodes in the same layer.
///// If v is the left neighbor of w, then Gў has an edge f = e(v,w) with d( f ) = r(v,w) and
///// w( f ) = 0. This edge forces the nodes to be sufficiently
///// separated but does not affect the cost of the layout.
XLayoutGraph CreateXLayoutGraph(LayerArrays layerArrays) {
int nOfVerts = properLayeredGraph.NodeCount;
//create edges of XLayoutGraph
var edges = new List<IntEdge>();
foreach (LayerEdge e in properLayeredGraph.Edges) {
var n1 = new IntEdge(nOfVerts, e.Source);
var n2 = new IntEdge(nOfVerts, e.Target);
n1.Weight = n2.Weight = 1;
n1.Separation = 0; //these edge have 0 separation
n2.Separation = 0;
nOfVerts++;
edges.Add(n1);
edges.Add(n2);
}
foreach (var layer in layerArrays.Layers)
for (int i = layer.Length - 1; i > 0; i--) {
int source = layer[i];
int target = layer[i - 1];
var ie = new IntEdge(source, target);
Anchor sourceAnchor = database.Anchors[source];
Anchor targetAnchor = database.Anchors[target];
double sep = sourceAnchor.LeftAnchor + targetAnchor.RightAnchor + sugiyamaSettings.NodeSeparation;
ie.Separation = (int)(sep + 1);
edges.Add(ie);
}
var ret = new XLayoutGraph(IntGraph, properLayeredGraph, layerArrays, edges, nOfVerts);
ret.SetEdgeWeights();
return ret;
}
static LayerArrays RecoverOriginalLayersAndSettings(GeometryGraph geometryGraph, out SugiyamaLayoutSettings sugiyamaLayoutSettings) {
sugiyamaLayoutSettings = new SugiyamaLayoutSettings();
return RecoverOriginalHorizontalLayers(geometryGraph, sugiyamaLayoutSettings) ??
RecoverOriginalVerticalLayers(geometryGraph, sugiyamaLayoutSettings);
}
static LayerArrays RecoverOriginalHorizontalLayers(GeometryGraph geometryGraph, SugiyamaLayoutSettings sugiyamaLayoutSettings) {
var nodes = geometryGraph.Nodes;
var list = new List<int>();
for (int i = 0; i < nodes.Count; i++)
list.Add(i);
var layers = list.GroupBy(i => nodes[i].Center.Y);
var layerList = new List<int[]>();
foreach (var layer in layers)
layerList.Add(layer.ToArray());
sugiyamaLayoutSettings.LayerSeparation = double.PositiveInfinity;
layerList = new List<int[]>(layerList.OrderBy(l => nodes[l[0]].Center.Y));
//check that the layers are separated
for (int i = 0; i < layerList.Count - 1; i++) {
var topOfI = layerList[i].Max(j => nodes[j].BoundingBox.Top);
var bottomOfINext = layerList[i + 1].Min(j => nodes[j].BoundingBox.Bottom);
var layerSep = bottomOfINext - topOfI;
if (layerSep <= 0) {
sugiyamaLayoutSettings.Transformation = null;
return null;
}
sugiyamaLayoutSettings.LayerSeparation = Math.Min(sugiyamaLayoutSettings.LayerSeparation, layerSep);
}
var nodesToLayers = new int[nodes.Count];
for (int i = 0; i < layerList.Count; i++) {
var layer = layerList[i];
for (int j = 0; j < layer.Length; j++)
nodesToLayers[layer[j]] = i;
}
sugiyamaLayoutSettings.Transformation = geometryGraph.Edges.Any(e => e.Source.Center.Y > e.Target.Center.Y)
? new PlaneTransformation()
: PlaneTransformation.Rotation(Math.PI);
return new LayerArrays(nodesToLayers);
}
static LayerArrays RecoverOriginalVerticalLayers(GeometryGraph geometryGraph, SugiyamaLayoutSettings sugiyamaLayoutSettings) {
var nodes = geometryGraph.Nodes;
var list = new List<int>();
for (int i = 0; i < geometryGraph.Nodes.Count; i++) list.Add(i);
var layers = list.GroupBy(i => nodes[i].Center.X);
var layerList = new List<int[]>();
foreach (var layer in layers) {
layerList.Add(layer.ToArray());
}
layerList = new List<int[]>(layerList.OrderBy(l => nodes[l[0]].Center.X));
sugiyamaLayoutSettings.LayerSeparation = double.PositiveInfinity;
//check that the layers are separated
for (int i = 0; i < layerList.Count - 1; i++) {
var rightOfI = layerList[i].Max(j => nodes[j].BoundingBox.Right);
var leftOfNext = layerList[i + 1].Min(j => nodes[j].BoundingBox.Left);
var layerSep = leftOfNext - rightOfI;
if (layerSep <= 0) {
return null;
}
sugiyamaLayoutSettings.LayerSeparation = Math.Min(sugiyamaLayoutSettings.LayerSeparation, layerSep);
}
var nodesToLayers = new int[nodes.Count];
for (int i = 0; i < layerList.Count; i++) {
var layer = layerList[i];
for (int j = 0; j < layer.Length; j++)
nodesToLayers[layer[j]] = i;
}
sugiyamaLayoutSettings.Transformation = geometryGraph.Edges.Any(e => e.Source.Center.X > e.Target.Center.X)
? PlaneTransformation.Rotation(-Math.PI/2)
: PlaneTransformation.Rotation(Math.PI/2);
return new LayerArrays(nodesToLayers);
}
public LayeredLayoutEngine GetEngine() {
if (recoveredLayerArrays == null) return null; //it is not a layered layout
if (!sugiyamaSettings.Transformation.IsIdentity) {
originalGraph.Transform(sugiyamaSettings.Transformation.Inverse);
}
FillLayersToRecoveredYCoordinates();
CycleRemoval();
Run();
var engine= CreateEngine();
if (!sugiyamaSettings.Transformation.IsIdentity) {
originalGraph.Transform(sugiyamaSettings.Transformation);
}
return engine;
}
LayeredLayoutEngine CreateEngine() {
return new LayeredLayoutEngine(engineLayerArrays, originalGraph, properLayeredGraph,
sugiyamaSettings, database, IntGraph, nodeIdToIndex, GluedDagSkeletonForLayering,
LayersAreDoubled, constrainedOrdering, Brandes, xLayoutGraph);
}
}
}