Satsuma
a delicious .NET graph library
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Properties Pages
Layout.cs
Go to the documentation of this file.
1 #region License
2 /*This file is part of Satsuma Graph Library
3 Copyright © 2013 Balázs Szalkai
4 
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12 
13  1. The origin of this software must not be misrepresented; you must not
14  claim that you wrote the original software. If you use this software
15  in a product, an acknowledgment in the product documentation would be
16  appreciated but is not required.
17 
18  2. Altered source versions must be plainly marked as such, and must not be
19  misrepresented as being the original software.
20 
21  3. This notice may not be removed or altered from any source
22  distribution.*/
23 #endregion
24 
25 using System;
26 using System.Collections.Generic;
27 using System.Linq;
28 using System.Xml.Linq;
29 using System.Drawing;
30 using System.Globalization;
31 
32 namespace Satsuma.Drawing
33 {
35  public struct PointD : IEquatable<PointD>
36  {
37  public double X { get; private set; }
38  public double Y { get; private set; }
39 
40  public PointD(double x, double y)
41  : this()
42  {
43  X = x;
44  Y = y;
45  }
46 
47  public bool Equals(PointD other)
48  {
49  return X == other.X && Y == other.Y;
50  }
51 
52  public override bool Equals(object obj)
53  {
54  if (!(obj is PointD)) return false;
55  return Equals((PointD)obj);
56  }
57 
58  public static bool operator ==(PointD a, PointD b)
59  {
60  return a.Equals(b);
61  }
62 
63  public static bool operator !=(PointD a, PointD b)
64  {
65  return !(a == b);
66  }
67 
68  public override int GetHashCode()
69  {
70  return (X.GetHashCode() * 17) + Y.GetHashCode();
71  }
72 
73  public string ToString(IFormatProvider provider)
74  {
75  return string.Format(provider, "({0} {1})", X, Y);
76  }
77 
78  public override string ToString()
79  {
80  return ToString(CultureInfo.CurrentCulture);
81  }
82 
84  public static PointD operator +(PointD a, PointD b)
85  {
86  return new PointD(a.X + b.X, a.Y + b.Y);
87  }
88 
90  public static PointD Add(PointD a, PointD b)
91  {
92  return a + b;
93  }
94 
95  public static explicit operator PointF(PointD self)
96  {
97  return new PointF((float)self.X, (float)self.Y);
98  }
99 
101  public static PointF ToPointF(PointD self)
102  {
103  return (PointF)self;
104  }
105 
107  public double Distance(PointD other)
108  {
109  return Math.Sqrt((X - other.X) * (X - other.X) + (Y - other.Y) * (Y - other.Y));
110  }
111  }
112 
138  public sealed class ForceDirectedLayout
139  {
141  public const double DefaultStartingTemperature = 0.2;
143  public const double DefaultMinimumTemperature = 0.01;
145  public const double DefaultTemperatureAttenuation = 0.95;
146 
148  public IGraph Graph { get; private set; }
150  public Dictionary<Node, PointD> NodePositions { get; private set; }
156  public Func<double, double> SpringForce { get; set; }
162  public Func<double, double> ElectricForce { get; set; }
164  public double Temperature { get; set; }
166  public double TemperatureAttenuation { get; set; }
167 
168  public ForceDirectedLayout(IGraph graph, Func<Node, PointD> initialPositions = null)
169  {
170  Graph = graph;
171  NodePositions = new Dictionary<Node, PointD>();
172  SpringForce = (d => 2 * Math.Log(d));
173  ElectricForce = (d => 1 / (d * d));
174  TemperatureAttenuation = DefaultTemperatureAttenuation;
175 
176  Initialize(initialPositions);
177  }
178 
181  public void Initialize(Func<Node, PointD> initialPositions = null)
182  {
183  if (initialPositions == null)
184  {
185  // make a random layout
186  Random r = new Random();
187  initialPositions = (node => new PointD(r.NextDouble(), r.NextDouble()));
188  }
189 
190  foreach (var node in Graph.Nodes())
191  NodePositions[node] = initialPositions(node);
192 
193  // reset the temperature
194  Temperature = DefaultStartingTemperature;
195  }
196 
198  public void Step()
199  {
200  Dictionary<Node, PointD> forces = new Dictionary<Node, PointD>();
201 
202  foreach (var u in Graph.Nodes())
203  {
204  PointD uPos = NodePositions[u];
205  double xForce = 0, yForce = 0;
206  // attraction forces
207  foreach (var arc in Graph.Arcs(u))
208  {
209  PointD vPos = NodePositions[Graph.Other(arc, u)];
210  double d = uPos.Distance(vPos);
211  double force = Temperature * SpringForce(d);
212  xForce += (vPos.X - uPos.X) / d * force;
213  yForce += (vPos.Y - uPos.Y) / d * force;
214  }
215  // repulsion forces
216  foreach (var v in Graph.Nodes())
217  {
218  if (v == u) continue;
219  PointD vPos = NodePositions[v];
220  double d = uPos.Distance(vPos);
221  double force = Temperature * ElectricForce(d);
222  xForce += (uPos.X - vPos.X) / d * force;
223  yForce += (uPos.Y - vPos.Y) / d * force;
224  }
225  forces[u] = new PointD(xForce, yForce);
226  }
227 
228  foreach (var node in Graph.Nodes())
229  NodePositions[node] += forces[node];
230  Temperature *= TemperatureAttenuation;
231  }
232 
234  public void Run(double minimumTemperature = DefaultMinimumTemperature)
235  {
236  while (Temperature > minimumTemperature) Step();
237  }
238  }
239 }