using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Msagl.Core;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Layout;
using Microsoft.Msagl.Core.Layout.ProximityOverlapRemoval;
using Microsoft.Msagl.Core.Layout.ProximityOverlapRemoval.MinimumSpanningTree;
using System.Threading.Tasks;
namespace Microsoft.Msagl.Layout.MDS {
///
/// Class for graph layout with multidimensional scaling.
///
public class MdsGraphLayout : AlgorithmBase {
readonly GeometryGraph graph;
readonly MdsLayoutSettings settings;
///
/// Constructs the multidimensional scaling algorithm.
///
public MdsGraphLayout(MdsLayoutSettings settings, GeometryGraph geometryGraph)
{
this.settings = settings;
graph = geometryGraph;
}
///
/// Executes the algorithm
///
protected override void RunInternal() {
LayoutConnectedComponents();
SetGraphBoundingBox();
}
/////
///// needed a comment
/////
/////
/////
//public void GroupSpread(GeometryGraph geometryGraph, Set group) {
// this.graph = geometryGraph;
// double[][] d = new double[group.Count][];
// double[] x, y;
// System.Console.WriteLine(group.Count);
// LayoutConnectedComponents(geometryGraph, out x, out y);
// int c = 0;
// foreach (Node node in group) {
// d[c] = Distances.SingleSourceUniformDistances(geometryGraph, node, false);
// c++;
// }
// double[][] w = MultidimensionalScaling.ExponentialWeightMatrix(d, -2);
// MultidimensionalScaling.DistanceScalingSubset(d, x, y, w, 5);
// double scaleX = ((MdsLayoutSettings)geometryGraph.LayoutAlgorithmSettings).ScaleX;
// double scaleY = ((MdsLayoutSettings)geometryGraph.LayoutAlgorithmSettings).ScaleY;
// double rotate = ((MdsLayoutSettings)geometryGraph.LayoutAlgorithmSettings).Rotate;
// Transformation.Rotate(x, y, rotate);
// int index = 0;
// foreach (Node node in geometryGraph.Nodes) {
// node.Center = new Point((int)(x[index] * scaleX), (int)(y[index] * scaleY));
// node.BoundaryCurve = node.BoundaryCurve.Move(node.Center);
// index++;
// }
// RankingLayout.SetEdgeCurves(geometryGraph);
// SetGraphBoundingBox();
//}
void SetGraphBoundingBox() {
graph.BoundingBox = graph.PumpTheBoxToTheGraphWithMargins();
}
///
/// Scales a configuration such that the average edge length in the drawing
/// equals the average of the given edge lengths.
///
/// A graph.
/// Coordinates.
/// Coordinates.
static void ScaleToAverageEdgeLength(GeometryGraph g, double[] x, double[] y) {
var index = new Dictionary();
int c=0;
foreach(Node node in g.Nodes) {
index.Add(node, c);
c++;
}
double avgSum = 0, avgLength = 0;
foreach(Edge edge in g.Edges) {
int i=index[edge.Source];
int j=index[edge.Target];
avgSum += Math.Sqrt(Math.Pow(x[i] - x[j], 2) + Math.Pow(y[i] - y[j], 2));
avgLength += edge.Length;
}
if(avgLength>0) avgSum /= avgLength;
if(avgSum>0)
for (int i = 0; i < x.Length; i++) {
x[i] /= avgSum;
y[i] /= avgSum;
}
}
///
/// Layouts a connected graph with Multidimensional Scaling, using
/// shortest-path distances as Euclidean target distances.
///
/// A graph.
/// The settings for the algorithm.
/// Coordinate vector.
/// Coordinate vector.
internal static void LayoutGraphWithMds(GeometryGraph geometryGraph, MdsLayoutSettings settings, out double[] x, out double[] y) {
x = new double[geometryGraph.Nodes.Count];
y = new double[geometryGraph.Nodes.Count];
if (geometryGraph.Nodes.Count == 0) return;
if (geometryGraph.Nodes.Count == 1)
{
x[0] = y[0] = 0;
return;
}
int k = Math.Min(settings.PivotNumber, geometryGraph.Nodes.Count);
int iter = settings.GetNumberOfIterationsWithMajorization(geometryGraph.Nodes.Count);
double exponent = settings.Exponent;
var pivotArray = new int[k];
PivotDistances pivotDistances = new PivotDistances(geometryGraph, false, pivotArray);
pivotDistances.Run();
double[][] c = pivotDistances.Result;
MultidimensionalScaling.LandmarkClassicalScaling(c, out x, out y, pivotArray);
ScaleToAverageEdgeLength(geometryGraph, x, y);
if (iter > 0) {
AllPairsDistances apd = new AllPairsDistances(geometryGraph, false);
apd.Run();
double[][] d = apd.Result;
double[][] w = MultidimensionalScaling.ExponentialWeightMatrix(d, exponent);
// MultidimensionalScaling.DistanceScaling(d, x, y, w, iter);
MultidimensionalScaling.DistanceScalingSubset(d, x, y, w, iter);
}
}
// class GeometryGraphComparer : IComparer {
// public int Compare(GeometryGraph g1, GeometryGraph g2) {
// return g2.Nodes.Count.CompareTo(g1.Nodes.Count);
// }
//}
///
/// Computes layout for possibly disconnected graphs by putting
/// the layouts for connected components together.
///
internal void LayoutConnectedComponents() {
GeometryGraph[] graphs = GraphConnectedComponents.CreateComponents(graph.Nodes, graph.Edges).ToArray();
// layout components, compute bounding boxes
if (settings.RunInParallel) {
ParallelOptions options = new ParallelOptions();
#if PPC
if (this.CancelToken != null)
options.CancellationToken = this.CancelToken.CancellationToken;
#endif
System.Threading.Tasks.Parallel.ForEach(graphs, options, LayoutConnectedGraphWithMds);
}
else
for (int i = 0; i < graphs.Length; i++) {
Console.WriteLine("laying out {0} connected component", i);
LayoutConnectedGraphWithMds(graphs[i]);
}
if (graphs.Length > 1) {
Console.WriteLine("packing");
PackGraphs(graphs, settings);
Console.WriteLine("done packing");
//restore the parents
foreach (var node in graphs.SelectMany(g => g.Nodes))
node.GeometryParent = graph;
}
Console.WriteLine("done with LayoutConnectedComponents");
}
void LayoutConnectedGraphWithMds(GeometryGraph compGraph)
{
Console.WriteLine("LayoutConnectedGraphWithMds: nodes {0} edges {1}", compGraph.Nodes.Count(), compGraph.Edges.Count());
double[] x, y;
LayoutGraphWithMds(compGraph, settings, out x, out y);
if (settings.RotationAngle != 0)
Transform.Rotate(x, y, settings.RotationAngle);
double scaleX = settings.ScaleX;
double scaleY = settings.ScaleY;
int index = 0;
foreach (Node node in compGraph.Nodes)
{
node.Center = new Point(x[index] * scaleX, y[index] * scaleY);
index++;
if ((index % 100) == 0)
{
ProgressStep();
}
}
if (settings.AdjustScale)
AdjustScale(compGraph.Nodes);
if (settings.RemoveOverlaps)
{
switch (settings.OverlapRemovalMethod)
{
case OverlapRemovalMethod.Prism:
ProximityOverlapRemoval.RemoveOverlaps(compGraph, settings.NodeSeparation);
break;
case OverlapRemovalMethod.MinimalSpanningTree:
GTreeOverlapRemoval.RemoveOverlaps(compGraph.Nodes.ToArray(), settings.NodeSeparation);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
compGraph.BoundingBox = compGraph.PumpTheBoxToTheGraphWithMargins();
}
void AdjustScale(IList nodes) {
if (nodes.Count <= 5)
return; //we can have only a little bit of white space with a layout with only few nodes
int repetitions = 10;
var scale = 1.0;
var delta = 0.5;
var tree = BuildNodeTree(nodes);
var random = new Random(1);
do
{
const int minNumberOfHits = 6;
const int maxNumberOfHits = 15;
const int numberOfChecks = 100;
int hits = NumberOfHits(numberOfChecks, random, tree, maxNumberOfHits);
if (hits < minNumberOfHits)
scale /= 1+delta;
else if (hits > maxNumberOfHits)
scale *= 1+delta;
else {
// if ( maxRepetitions> repetitions )
// Console.WriteLine("reps={0} scale={1}, hits={2}", maxRepetitions-repetitions , scale, hits);
return;
}
delta /= 2;
//adjust the scale the graph
ScaleNodes(nodes, scale);
if (repetitions-- == 0)
return;
UpdateTree(tree);
} while (true);
}
void ScaleNodes(IList nodes, double scale) {
int i = 0;
foreach (Node node in nodes) {
node.Center *= scale;
i++;
if ((i%100) == 0)
ProgressStep();
}
}
static void UpdateTree(RectangleNode tree) {
if (tree.IsLeaf)
tree.Rectangle = tree.UserData.BoundingBox;
else {
UpdateTree(tree.Left);
UpdateTree(tree.Right);
tree.rectangle = tree.Left.rectangle;
tree.rectangle.Add(tree.Right.rectangle);
}
}
static int NumberOfHits(int numberOfChecks, Random random, RectangleNode tree, int maxNumberOfHits) {
// var l = new List();
int numberOfHits = 0;
for (int i = 0; i < numberOfChecks; i++) {
Point point = RandomPointFromBox(random, ref tree.rectangle);
// l.Add(point);
if ((tree.FirstHitNode(point, (p, t) => HitTestBehavior.Stop)) != null)
numberOfHits++;
if (numberOfHits == maxNumberOfHits)
return maxNumberOfHits;
}
//LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(Getdc(tree, l));
return numberOfHits;
}
/*
static IEnumerable Getdc(RectangleNode tree, List points) {
foreach (var point in points)
yield return new DebugCurve("red", new Ellipse(5, 5, point));
foreach (var rn in tree.GetAllLeafNodes()) {
yield return new DebugCurve("blue", rn.Rectangle.Perimeter());
yield return new DebugCurve("green", rn.UserData.BoundaryCurve);
}
}
*/
static RectangleNode BuildNodeTree(IList nodes) {
return RectangleNode.CreateRectangleNodeOnEnumeration(
nodes.Select(n => new RectangleNode(n, n.BoundingBox)));
}
static Point RandomPointFromBox(Random random, ref Rectangle boundingBox) {
var x=random.NextDouble();
var y=random.NextDouble();
var p= new Point(boundingBox.Left + boundingBox.Width * x, boundingBox.Bottom + boundingBox.Height * y);
return p;
}
///
/// Pack the given graph components to the specified aspect ratio
///
/// set of graphs to pack
/// settings for packing method and desired aspect ratio
/// Bounding box of the packed components
public static Rectangle PackGraphs(IEnumerable components, LayoutAlgorithmSettings settings) {
List> rectangles =
(from c in components select new RectangleToPack(c.BoundingBox, c)).ToList();
if (rectangles.Count > 1) {
OptimalPacking packing = settings.PackingMethod == PackingMethod.Compact
? new OptimalRectanglePacking(rectangles, settings.PackingAspectRatio)
: (OptimalPacking)
new OptimalColumnPacking(rectangles, settings.PackingAspectRatio);
packing.Run();
foreach (var r in rectangles) {
GeometryGraph component = r.Data;
var delta = r.Rectangle.LeftBottom - component.boundingBox.LeftBottom;
component.Translate(delta);
}
return new Rectangle(0, 0, packing.PackedWidth, packing.PackedHeight);
}
if (rectangles.Count == 1)
return rectangles[0].Rectangle;
return Rectangle.CreateAnEmptyBox();
}
}
}