#region License Information /* HeuristicLab * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.Linq; namespace HeuristicLab.Networks.Views.NetworkVisualization { public class Layout { private const double RAD_TO_DEG = 180 / Math.PI; private const double DEG_TO_RAD = 1 / RAD_TO_DEG; private const double REPULSION_CONSTANT = 10000.0; private const double ATTRACTION_CONSTANT = 0.1; private const double EQUILIBRIUM = 15; private readonly IList nodes = new List(); public bool AddNode(Node node) { if (node == null) throw new ArgumentNullException("node"); if (nodes.Contains(node)) return false; nodes.Add(node); foreach (var c in node.Nodes) AddNode(c); return true; } public void Arrange(double damping = 0.5, double springLength = 100, int maxIterations = 500, bool deterministic = true) { var random = deterministic ? new Random(0) : new Random(); var nodeMechanics = new NodeMechanics[nodes.Count]; for (int i = 0; i < nodes.Count; i++) { nodeMechanics[i] = new NodeMechanics(nodes[i], new Vector(), Point.Empty); nodeMechanics[i].Node.Location = new Point(random.Next(-50, 50), random.Next(-50, 50)); } for (int i = 0, eqCount = 0; i < maxIterations && eqCount <= EQUILIBRIUM; i++) { double totalDisplacement = 0.0; foreach (var current in nodeMechanics) { var node = current.Node; var currentPosition = new Vector(CalculateDistance(Point.Empty, node.Location), CalculateBearingAngle(Point.Empty, node.Location)); var netForce = new Vector(); foreach (var n in nodes) if (n != node) netForce += CalculateRepulsionForce(node, n); foreach (var n in node.Nodes) netForce += CalculateAttractionForce(node, n, springLength); foreach (var n in nodes) if (n.Nodes.Contains(node)) netForce += CalculateAttractionForce(node, n, springLength); current.Velocity = (current.Velocity + netForce) * damping; current.NextPosition = (currentPosition + current.Velocity).ToPoint(); } foreach (var current in nodeMechanics) { totalDisplacement += CalculateDistance(current.Node.Location, current.NextPosition); current.Node.Location = current.NextPosition; } if (totalDisplacement < 10) eqCount++; } } public void Arrange(Rectangle bounds, double damping = 0.5, double springLength = 100.0, int maxIterations = 500, bool deterministic = true) { Arrange(damping, 200, maxIterations, deterministic); var logicalBounds = GetLogicalBounds(); var center = new Point(logicalBounds.X + logicalBounds.Width / 2, logicalBounds.Y + logicalBounds.Height / 2); int maxWidth = 0, maxHeight = 0; foreach (var node in nodes) { node.Location -= (Size)center; if (node.Size.Width > maxWidth) maxWidth = node.Size.Width; if (node.Size.Height > maxHeight) maxHeight = node.Size.Height; } bounds = new Rectangle(bounds.X + maxWidth / 2, bounds.Y + maxHeight / 2, bounds.Width - maxWidth, bounds.Height - maxHeight); double scale = Math.Min((double)bounds.Width / logicalBounds.Width, (double)bounds.Height / logicalBounds.Height); foreach (var node in nodes) node.Location = ScalePoint(node.Location, scale); logicalBounds = GetLogicalBounds(); center = new Point(logicalBounds.X + logicalBounds.Width / 2, logicalBounds.Y + logicalBounds.Height / 2); center.Offset(-(bounds.X + bounds.Width / 2), bounds.Y + bounds.Height / 2); foreach (var node in nodes) node.Location -= (Size)center; } #region Forces private Vector CalculateRepulsionForce(Node a, Node b) { double r = Math.Max(CalculateDistance(a.Location, b.Location), 1.0); double force = -(REPULSION_CONSTANT / (r * r)); double angle = CalculateBearingAngle(a.Location, b.Location); return new Vector(force, angle); } private Vector CalculateAttractionForce(Node a, Node b, double springLength) { double r = Math.Max(CalculateDistance(a.Location, b.Location), 1.0); double force = ATTRACTION_CONSTANT * Math.Max(r - springLength, 0.0); double angle = CalculateBearingAngle(a.Location, b.Location); return new Vector(force, angle); } #endregion #region Helpers private static double CalculateDistance(Point a, Point b) { double dX = a.X - b.X; double dY = a.Y - b.Y; return Math.Sqrt(dX * dX + dY * dY); } private static double CalculateBearingAngle(Point a, Point b) { double dX = b.X - a.X; double dY = b.Y - a.Y; double angle = Math.Atan2(dY, dX) * RAD_TO_DEG; return angle; } private static Point ScalePoint(Point p, double scalar) { return new Point((int)Math.Round(p.X * scalar), (int)Math.Round(p.Y * scalar)); } private Rectangle GetLogicalBounds() { int minX = int.MaxValue, maxX = int.MinValue, minY = int.MaxValue, maxY = int.MinValue; foreach (var node in nodes) { if (node.Location.X < minX) minX = node.Location.X; if (node.Location.X > maxX) maxX = node.Location.X; if (node.Location.Y < minY) minY = node.Location.Y; if (node.Location.Y > maxY) maxY = node.Location.Y; } return Rectangle.FromLTRB(minX, minY, maxX, maxY); } #endregion #region Vector private struct Vector { public double X { get; private set; } public double Y { get; private set; } public Vector(double magnitude, double direction) : this() { X = magnitude * Math.Cos(direction * DEG_TO_RAD); Y = magnitude * Math.Sin(direction * DEG_TO_RAD); } public Point ToPoint() { return new Point((int)Math.Round(X), (int)Math.Round(Y)); } public static Vector operator +(Vector a, Vector b) { return new Vector { X = a.X + b.X, Y = a.Y + b.Y }; } public static Vector operator *(Vector v, double d) { return new Vector { X = v.X * d, Y = v.Y * d }; } } #endregion #region Nodes public abstract class Node { public Point Location { get; set; } public abstract Size Size { get; } private readonly List nodes = new List(); public IEnumerable Nodes { get { return nodes; } } public bool AddNode(Node node) { if (node == null) throw new ArgumentNullException("node"); if (node == this || nodes.Contains(node)) return false; nodes.Add(node); return true; } } public class LayoutNode : Node { private Size size; public override Size Size { get { return size; } } public LayoutNode(Size size) { this.size = size; } } #endregion #region Mechanics private class NodeMechanics { public Node Node { get; private set; } public Vector Velocity { get; set; } public Point NextPosition { get; set; } public NodeMechanics(Node node, Vector velocity, Point nextPosition) { Node = node; Velocity = velocity; NextPosition = nextPosition; } } #endregion } }