Skip to content

Commit

Permalink
Merge pull request #636 from uni-bremen-agst/563-incremental-layout
Browse files Browse the repository at this point in the history
563 incremental layout
  • Loading branch information
koschke authored Sep 20, 2023
2 parents b8812d3 + 0c0612c commit 89307f3
Show file tree
Hide file tree
Showing 42 changed files with 2,390 additions and 9 deletions.
2 changes: 0 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,3 @@ Assets/StreamingAssets/Videos.meta filter=lfs diff=lfs merge=lfs -text
Assets/StreamingAssets/Videos/** filter=lfs diff=lfs merge=lfs -text
Assets/Trailer/** filter=lfs diff=lfs merge=lfs -text
Assets/Resources/External/** filter=lfs diff=lfs merge=lfs -text


3 changes: 3 additions & 0 deletions Assets/Plugins/MathNet.Numerics.dll

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Assets/Plugins/MathNet.Numerics.dll.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Assets/SEE/Game/City/AbstractSEECityExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public static NodelayoutModel GetModel(this NodeLayoutKind nodeLayout)
return new NodelayoutModel(OnlyLeaves: false, CanApplySublayouts: false, InnerNodesEncloseLeafNodes: true, IsCircular: false, isHierarchical: true);
case NodeLayoutKind.Treemap:
return new NodelayoutModel(OnlyLeaves: false, CanApplySublayouts: false, InnerNodesEncloseLeafNodes: true, IsCircular: false, isHierarchical: true);
case NodeLayoutKind.IncrementalTreeMap:
return new NodelayoutModel(OnlyLeaves: false, CanApplySublayouts: false, InnerNodesEncloseLeafNodes: true, IsCircular: false, isHierarchical: true);
case NodeLayoutKind.CirclePacking:
return new NodelayoutModel(OnlyLeaves: false, CanApplySublayouts: false, InnerNodesEncloseLeafNodes: true, IsCircular: true, isHierarchical: true);
case NodeLayoutKind.Manhattan:
Expand Down
145 changes: 145 additions & 0 deletions Assets/SEE/Game/City/IncrementalTreeMapSetting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using SEE.Utils;
using Sirenix.OdinInspector;
using UnityEngine;

namespace SEE.Game.City
{
/// <summary>
/// The settings for <see cref="Layout.NodeLayouts.IncrementalTreeMapLayout"/>.
/// </summary>
[Serializable]
public class IncrementalTreeMapSetting : ConfigIO.PersistentConfigItem
{
/// <summary>
/// The depth of the local moves search.
/// </summary>
[SerializeField]
[Range(0, 5)]
[Tooltip("The maximal depth for local moves algorithm. Increase for higher visual quality, " +
"decrease for higher stability and to save runtime")]
public int LocalMovesDepth = 3;

/// <summary>
/// The maximal branching factor of the local moves search.
/// </summary>
[SerializeField]
[Range(1, 10)]
[Tooltip("The maximal branching factor for local moves algorithm. Increase for higher visual quality, " +
"decrease for higher stability and to save runtime")]
public int LocalMovesBranchingLimit = 4;

/// <summary>
/// Defines the specific p norm used in the local moves algorithm. See here:
/// <see cref="Layout.NodeLayouts.IncrementalTreeMap.LocalMoves.AspectRatiosPNorm"/>.
///
/// Notice:
/// The kind of p norm changes which layout is considered to have the greatest visual quality.
/// For example with p=1 (Manhattan Norm) the algorithm would
/// minimize the sum of aspect ratios, while with p=infinity (Chebyshev Norm)
/// the algorithm would minimize the maximal aspect ratio over the layout nodes.
/// The other p norms range between these extremes.
///
/// Needs therefor a mapping from <see cref="PNormRange"/> to a double value p, which is realized with the
/// property <see cref="PNorm"/>.
/// </summary>
[SerializeField]
[Tooltip("Norm for the visual quality of a set of nodes, " +
"larger p values lead to stronger penalties for larger deviations in aspect ratio of single nodes.")]
private PNormRange pNorm = PNormRange.P2Euclidean;

/// <summary>
/// The absolute padding between neighboring nodes so that they can be distinguished (in millimeters).
/// </summary>
[SerializeField]
[Range(0.1f, 100f)]
[LabelText("Padding (mm)")]
[Tooltip("The distance between two neighbour nodes in mm")]
public float PaddingMm = 5f;

/// <summary>
/// The maximal error for the method
/// <see cref="Layout.NodeLayouts.IncrementalTreeMap.CorrectAreas.GradientDecent"/> as power of 10.
/// </summary>
[SerializeField]
[Range(-7, -2)]
[LabelText("Gradient Descent Precision (10^n)")]
[Tooltip("The maximal error for the gradient descent method as power of 10")]
public int GradientDescentPrecisionExponent = -4;

/// <summary>
/// Maps <see cref="pNorm"/> to a double.
/// </summary>
public double PNorm => pNorm switch
{
(PNormRange.P1Manhattan) => 1d,
(PNormRange.P2Euclidean) => 2d,
(PNormRange.P3) => 3d,
(PNormRange.P4) => 4d,
(PNormRange.PInfinityChebyshev) => double.PositiveInfinity,
_ => throw new InvalidEnumArgumentException("Unrecognized PNormRange value.")
};

public void Save(ConfigWriter writer, string label)
{
writer.BeginGroup(label);
writer.Save(LocalMovesDepth, LocalMovesDepthLabel);
writer.Save(LocalMovesBranchingLimit, LocalMovesBranchingLimitLabel);
writer.Save(pNorm.ToString(), PNormLabel);
writer.Save(GradientDescentPrecisionExponent, GradientDescentPrecisionLabel);
writer.Save(PaddingMm, PaddingLabel);
writer.EndGroup();
}

public bool Restore(Dictionary<string, object> attributes, string label)
{
if (!attributes.TryGetValue(label, out object dictionary))
{
return false;
}
Dictionary<string, object> values = dictionary as Dictionary<string, object>;
bool result = ConfigIO.Restore(values, LocalMovesDepthLabel, ref LocalMovesDepth);
result |= ConfigIO.Restore(values, LocalMovesBranchingLimitLabel, ref LocalMovesBranchingLimit);
result |= ConfigIO.RestoreEnum(values, PNormLabel, ref pNorm);
result |= ConfigIO.Restore(values, GradientDescentPrecisionLabel, ref GradientDescentPrecisionExponent);
result |= ConfigIO.Restore(values, PaddingLabel, ref PaddingMm);
return result;
}

/// <summary>
/// Configuration label for <see cref="LocalMovesDepth"/>.
/// </summary>
private const string LocalMovesDepthLabel = "LocalMovesDepth";
/// <summary>
/// Configuration label for <see cref="LocalMovesBranchingLimit"/>.
/// </summary>
private const string LocalMovesBranchingLimitLabel = "LocalMovesBranchingLimit";
/// <summary>
/// Configuration label for <see cref="PNorm"/>.
/// </summary>
private const string PNormLabel = "PNorm";
/// <summary>
/// Configuration label for <see cref="GradientDescentPrecisionExponent"/>.
/// </summary>
private const string GradientDescentPrecisionLabel = "GradientDescentPrecision";
/// <summary>
/// Configuration label for <see cref="PaddingMm"/>.
/// </summary>
private const string PaddingLabel = "Padding";
}

/// <summary>
/// Selection of possible PNorms. Used for better access in Unity Editor for the field
/// <see cref="IncrementalTreeMapSetting.pNorm"/>.
/// </summary>
public enum PNormRange
{
P1Manhattan,
P2Euclidean,
P3,
P4,
PInfinityChebyshev,
}
}
3 changes: 3 additions & 0 deletions Assets/SEE/Game/City/IncrementalTreeMapSetting.cs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 32bc90aa558d49f5b773a0e799a1bdbe
timeCreated: 1691856370
23 changes: 19 additions & 4 deletions Assets/SEE/Game/City/NodeLayoutAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public class NodeLayoutAttributes : LayoutSettings
/// </summary>
public NodeLayoutKind Kind = NodeLayoutKind.Balloon;

/// <summary>
/// Settings for the <see cref="SEE.Layout.NodeLayouts.IncrementalTreeMapLayout"/>.
/// </summary>
public IncrementalTreeMapSetting IncrementalTreeMapSetting = new();

/// <summary>
/// The path for the layout file containing the node layout information.
/// If the file extension is <see cref="Filenames.GVLExtension"/>, the layout is expected
Expand All @@ -24,15 +29,13 @@ public class NodeLayoutAttributes : LayoutSettings
/// data of a game object.
/// </summary>
[OdinSerialize]
public FilePath LayoutPath = new FilePath();

private const string LayoutPathLabel = "LayoutPath";

public FilePath LayoutPath = new();
public override void Save(ConfigWriter writer, string label)
{
writer.BeginGroup(label);
writer.Save(Kind.ToString(), NodeLayoutLabel);
LayoutPath.Save(writer, LayoutPathLabel);
IncrementalTreeMapSetting.Save(writer, IncrementalTreeMapLabel);
writer.EndGroup();
}

Expand All @@ -44,9 +47,21 @@ public override void Restore(Dictionary<string, object> attributes, string label

ConfigIO.RestoreEnum(values, NodeLayoutLabel, ref Kind);
LayoutPath.Restore(values, LayoutPathLabel);
IncrementalTreeMapSetting.Restore(values, IncrementalTreeMapLabel);
}
}

/// <summary>
/// Configuration label for <see cref="LayoutPath"/>.
/// </summary>
private const string LayoutPathLabel = "LayoutPath";
/// <summary>
/// Configuration label for <see cref="IncrementalTreeMapSetting"/>.
/// </summary>
private const string IncrementalTreeMapLabel = "IncrementalTreeMap";
/// <summary>
/// Configuration label for <see cref="Kind"/>.
/// </summary>
private const string NodeLayoutLabel = "NodeLayout";
}
}
1 change: 1 addition & 0 deletions Assets/SEE/Game/City/NodeLayoutKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum NodeLayoutKind : byte
CirclePacking,
Manhattan,
CompoundSpringEmbedder,
IncrementalTreeMap,
FromFile
}
}
14 changes: 14 additions & 0 deletions Assets/SEE/Game/Evolution/EvolutionRendererLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public partial class EvolutionRenderer
private IList<Dictionary<string, ILayoutEdge<ILayoutNode>>> EdgeLayouts { get; }
= new List<Dictionary<string, ILayoutEdge<ILayoutNode>>>();

/// <summary>
/// The last calculated <see cref="NodeLayout"/> used for <see cref="IncrementalTreeMapLayout"/>.
/// </summary>
private NodeLayout oldLayout = null;

/// <summary>
/// Creates and saves the node and edge layouts for all given <paramref name="graphs"/>. This will
/// also create all necessary game nodes and game edges-- even those game nodes and game edges
Expand Down Expand Up @@ -105,12 +110,21 @@ private void CalculateLayout(Graph graph)
gameObjects.Add(gameNode);
}

// Since incremental layouts must know the layout of the last revision
// but are also bound to the function calls of NodeLayout
// we must hand over this argument here separately
if (nodeLayout is IIncrementalNodeLayout iNodeLayout && oldLayout is IIncrementalNodeLayout iOldLayout)
{
iNodeLayout.OldLayout = iOldLayout;
}

// Calculate and apply the node layout
ICollection<LayoutGraphNode> layoutNodes = GraphRenderer.ToAbstractLayoutNodes(gameObjects);
// Note: Apply applies its results only on the layoutNodes but not on the game objects
// these layoutNodes represent. Here, we leave the game objects untouched. The layout
// must be later applied when we render a city. Here, we only store the layout for later use.
nodeLayout.Apply(layoutNodes);
oldLayout = nodeLayout;
GraphRenderer.Fit(gameObject, layoutNodes);
GraphRenderer.Stack(gameObject, layoutNodes);

Expand Down
5 changes: 5 additions & 0 deletions Assets/SEE/Game/GraphRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,11 @@ public NodeLayout GetLayout(GameObject parent) =>
NodeLayoutKind.RectanglePacking => new RectanglePackingNodeLayout(GroundLevel),
NodeLayoutKind.EvoStreets => new EvoStreetsNodeLayout(GroundLevel),
NodeLayoutKind.Treemap => new TreemapLayout(GroundLevel, parent.transform.lossyScale.x, parent.transform.lossyScale.z),
NodeLayoutKind.IncrementalTreeMap => new IncrementalTreeMapLayout(
GroundLevel,
parent.transform.lossyScale.x,
parent.transform.lossyScale.z,
Settings.NodeLayoutSettings.IncrementalTreeMapSetting),
NodeLayoutKind.Balloon => new BalloonNodeLayout(GroundLevel),
NodeLayoutKind.CirclePacking => new CirclePackingNodeLayout(GroundLevel),
NodeLayoutKind.CompoundSpringEmbedder => new CoseLayout(GroundLevel, Settings),
Expand Down
6 changes: 4 additions & 2 deletions Assets/SEE/Game/NodeRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ private float[] SelectMetrics(Node node)
else
{
Vector3 scale = GetScale(node);
if (Settings.NodeLayoutSettings.Kind == NodeLayoutKind.Treemap)
if (Settings.NodeLayoutSettings.Kind == NodeLayoutKind.Treemap
|| Settings.NodeLayoutSettings.Kind == NodeLayoutKind.IncrementalTreeMap)
{
// FIXME: This is ugly. The graph renderer should not need to care what
// kind of layout was applied.
Expand Down Expand Up @@ -351,7 +352,8 @@ public void AdjustScaleOfLeaf(GameObject gameNode)
Vector3 scale = GetScale(node);

// Scale according to the metrics.
if (Settings.NodeLayoutSettings.Kind == NodeLayoutKind.Treemap)
if (Settings.NodeLayoutSettings.Kind == NodeLayoutKind.Treemap
|| Settings.NodeLayoutSettings.Kind == NodeLayoutKind.IncrementalTreeMap)
{
// FIXME: This is ugly. The graph renderer should not need to care what
// kind of layout was applied.
Expand Down
6 changes: 6 additions & 0 deletions Assets/SEE/Layout/NodeLayouts/Cose/CoseHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ public static NodeLayout GetNodelayout(NodeLayoutKind nodeLayout, float groundLe
return new EvoStreetsNodeLayout(groundLevel);
case NodeLayoutKind.Treemap:
return new TreemapLayout(groundLevel, 1000.0f, 1000.0f);
case NodeLayoutKind.IncrementalTreeMap:
return new IncrementalTreeMapLayout(
groundLevel,
1000.0f,
1000.0f,
settings.NodeLayoutSettings.IncrementalTreeMapSetting);
case NodeLayoutKind.Balloon:
return new BalloonNodeLayout(groundLevel);
case NodeLayoutKind.CirclePacking:
Expand Down
19 changes: 19 additions & 0 deletions Assets/SEE/Layout/NodeLayouts/IIncrementalNodeLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace SEE.Layout.NodeLayouts
{
/// <summary>
/// Defines the interface for incremental node layouts.
///
/// Incremental node layouts are designed for the animation of evolution
/// and each layout depends on the layout of the last revision.
///
/// This interface extends a layout by <see cref="OldLayout"/>
/// to hand over the layout of the last revision.
/// </summary>
public interface IIncrementalNodeLayout
{
/// <summary>
/// Setter for the layout of the last revision.
/// </summary>
IIncrementalNodeLayout OldLayout { set; }
}
}
11 changes: 11 additions & 0 deletions Assets/SEE/Layout/NodeLayouts/IIncrementalNodeLayout.cs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c08a007a03783def3883f51a6fecb1b5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
8 changes: 8 additions & 0 deletions Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3d3e0b38dff2821d59a3dde1e44d24c9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Loading

0 comments on commit 89307f3

Please sign in to comment.