mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-03-03 00:00:57 +08:00
自动布局的代码,完成中
This commit is contained in:
69
AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs
Normal file
69
AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//using System;
|
||||||
|
|
||||||
|
//namespace AIStudio.Wpf.DiagramDesigner.Layout
|
||||||
|
//{
|
||||||
|
// public class Configuration
|
||||||
|
// {
|
||||||
|
// /// <summary>
|
||||||
|
// /// The network whose nodes are to be repositioned.
|
||||||
|
// /// </summary>
|
||||||
|
// public DiagramViewModel Network { get; set; }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// A time modifier that is used to speed up, or slow down, time during the simulation.
|
||||||
|
// /// A greater time modifier speeds up the physics simulation, at the cost of accuracy and stability.
|
||||||
|
// /// </summary>
|
||||||
|
// public float TimeModifier { get; set; } = 3.5f;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Number of updates per iteration.
|
||||||
|
// /// Increasing this number increases the accuracy of the physics simulation at the cost of performance.
|
||||||
|
// /// </summary>
|
||||||
|
// public int UpdatesPerIteration { get; set; } = 1;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// How strongly should nodes push eachother away?
|
||||||
|
// /// A greater NodeRepulsionForce increases the distance between nodes.
|
||||||
|
// /// </summary>
|
||||||
|
// public float NodeRepulsionForce { get; set; } = 100;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// A function that maps each connection onto the equilibrium distance of its corresponding spring.
|
||||||
|
// /// A greater equilibrium distance increases the distance between the two connected endpoints.
|
||||||
|
// /// </summary>
|
||||||
|
// public Func<ConnectionViewModel, double> EquilibriumDistance { get; set; } = conn => 100;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// A function that maps each connection onto the springiness/stiffness constant of its corresponding spring.
|
||||||
|
// /// (c.f. Hooke's law)
|
||||||
|
// /// </summary>
|
||||||
|
// public Func<ConnectionViewModel, double> SpringConstant { get; set; } = conn => 1;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// A function that maps each connection onto the strength of its row force.
|
||||||
|
// /// Since inputs/outputs are on the left/right of a node, networks tend to be layed out horizontally.
|
||||||
|
// /// The row force is added onto the endpoints of the connection to make the nodes end up in a more horizontal layout.
|
||||||
|
// /// </summary>
|
||||||
|
// public Func<ConnectionViewModel, double> RowForce { get; set; } = conn => 100;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// A function that maps each node onto its mass in the physics simulation.
|
||||||
|
// /// Greater mass makes the node harder to move.
|
||||||
|
// /// </summary>
|
||||||
|
// public Func<DesignerItemViewModelBase, float> NodeMass { get; set; } = node => 10;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// The friction coefficient is used to control friction forces in the simulation.
|
||||||
|
// /// Greater friction makes the simulation converge faster, as it slows nodes down when
|
||||||
|
// /// they are swinging around. If the friction is too high, the nodes will stop moving before
|
||||||
|
// /// they reach their optimal position or might not even move at all.
|
||||||
|
// /// </summary>
|
||||||
|
// public Func<DesignerItemViewModelBase, float> FrictionCoefficient { get; set; } = node => 2.5f;
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// A predicate function that specifies whether or not a node is fixed.
|
||||||
|
// /// Fixed nodes do not get moved in the simulation.
|
||||||
|
// /// </summary>
|
||||||
|
// public Func<DesignerItemViewModelBase, bool> IsFixedNode { get; set; } = node => false;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
123
AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs
Normal file
123
AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
//using System;
|
||||||
|
//using System.Collections.Generic;
|
||||||
|
//using System.Linq;
|
||||||
|
//using System.Windows;
|
||||||
|
//using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
||||||
|
|
||||||
|
//namespace AIStudio.Wpf.DiagramDesigner.Layout
|
||||||
|
//{
|
||||||
|
// internal class Engine
|
||||||
|
// {
|
||||||
|
// internal void ApplyRandomShift(DiagramViewModel network)
|
||||||
|
// {
|
||||||
|
// Random random = new Random();
|
||||||
|
// foreach (var node in network.Items.OfType<DesignerItemViewModelBase>())
|
||||||
|
// {
|
||||||
|
// node.Position = node.Position + new VectorBase(random.NextDouble(), random.NextDouble());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// internal void Update(int deltaTMillis, IState state, Configuration config)
|
||||||
|
// {
|
||||||
|
// // Calculate forces
|
||||||
|
// int nodeCount = config.Network.Items.Count;
|
||||||
|
// IList<(DesignerItemViewModelBase, VectorBase)> nodeForces = new List<(DesignerItemViewModelBase, VectorBase)>(nodeCount);
|
||||||
|
|
||||||
|
// foreach (var node in config.Network.Items.OfType<DesignerItemViewModelBase>())
|
||||||
|
// {
|
||||||
|
// if (!config.IsFixedNode(node))
|
||||||
|
// {
|
||||||
|
// nodeForces.Add((node, CalculateNodeForce(node, state, config)));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Apply forces
|
||||||
|
// foreach (var (node, force) in nodeForces)
|
||||||
|
// {
|
||||||
|
// VectorBase speed = state.GetNodeSpeed(node);
|
||||||
|
// VectorBase pos = state.GetNodePosition(node);
|
||||||
|
// double deltaT = deltaTMillis / 1000.0;
|
||||||
|
// state.SetNodePosition(node, pos + ((speed * deltaT) + (force * deltaT * deltaT / 2)));
|
||||||
|
// state.SetNodeSpeed(node, speed + ((force / config.NodeMass(node)) * deltaT));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private VectorBase CalculateNodeForce(DesignerItemViewModelBase node, IState state, Configuration config)
|
||||||
|
// {
|
||||||
|
// VectorBase force = new VectorBase();
|
||||||
|
|
||||||
|
// // Calculate total force on node from endpoints
|
||||||
|
// if (node.InputConnectors.Count > 0 || node.OutputConnectors.Count > 0)
|
||||||
|
// {
|
||||||
|
// force += node.InputConnectors.Concat(node.OutputConnectors)
|
||||||
|
// .Select(e => CalculateEndpointForce(e, state, config))
|
||||||
|
// .Aggregate((v1, v2) => v1 + v2);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Apply node repulsion force so nodes don't overlap
|
||||||
|
// var nodeCenter = state.GetNodePosition(node) + (new VectorBase(node.Size.Width, node.Size.Height) / 2.0);
|
||||||
|
// foreach (var otherNode in config.Network.Items.OfType<DesignerItemViewModelBase>())
|
||||||
|
// {
|
||||||
|
// if (node == otherNode)
|
||||||
|
// {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var otherNodeCenter = state.GetNodePosition(otherNode) + (new VectorBase(otherNode.Size.Width, otherNode.Size.Height) / 2.0);
|
||||||
|
// var thisToOther = otherNodeCenter - nodeCenter;
|
||||||
|
// var dist = thisToOther.Length;
|
||||||
|
// thisToOther.Normalize();
|
||||||
|
|
||||||
|
// var repulsionX = thisToOther.X * (-1 * ((node.Size.Width + otherNode.Size.Width) / 2) / dist);
|
||||||
|
// var repulsionY = thisToOther.Y * (-1 * ((node.Size.Height + otherNode.Size.Height) / 2) / dist);
|
||||||
|
// force += new VectorBase(repulsionX, repulsionY) * config.NodeRepulsionForce;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Apply friction to make the movement converge to a stable state.
|
||||||
|
// float gravity = 9.8f;
|
||||||
|
// float normalForce = gravity * config.NodeMass(node);
|
||||||
|
// float kineticFriction = normalForce * config.FrictionCoefficient(node);
|
||||||
|
// VectorBase frictionVector = new VectorBase();
|
||||||
|
// var nodeSpeed = state.GetNodeSpeed(node);
|
||||||
|
// if (nodeSpeed.Length > 0)
|
||||||
|
// {
|
||||||
|
// frictionVector = new VectorBase(nodeSpeed.X, nodeSpeed.Y);
|
||||||
|
// frictionVector.Normalize();
|
||||||
|
// frictionVector *= -1.0 * kineticFriction;
|
||||||
|
// }
|
||||||
|
// force += frictionVector;
|
||||||
|
|
||||||
|
// return force;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private VectorBase CalculateEndpointForce(ConnectorInfoBase endpoint, IState state, Configuration config)
|
||||||
|
// {
|
||||||
|
// var pos = state.GetEndpointPosition(endpoint);
|
||||||
|
|
||||||
|
// VectorBase force = new VectorBase();
|
||||||
|
|
||||||
|
// foreach (var conn in endpoint.Connections.Items)
|
||||||
|
// {
|
||||||
|
// var otherSide = conn.Input == endpoint ? (ConnectorInfoBase)conn.Output : conn.Input;
|
||||||
|
// var otherSidePos = state.GetEndpointPosition(otherSide);
|
||||||
|
// var dist = (otherSidePos - pos).Length;
|
||||||
|
// var angle = Math.Acos((otherSidePos.X - pos.X) / dist);
|
||||||
|
// if (otherSidePos.Y < pos.Y)
|
||||||
|
// {
|
||||||
|
// angle *= -1.0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Put a spring between connected endpoints.
|
||||||
|
// var hookForce = (dist - config.EquilibriumDistance(conn)) * config.SpringConstant(conn);
|
||||||
|
// force += new VectorBase(Math.Cos(angle), Math.Sin(angle)) * hookForce;
|
||||||
|
|
||||||
|
// // Try to 'straighten' out the graph horizontally.
|
||||||
|
// var isLeftSide = endpoint.Orientation == ConnectorOrientation.Left;
|
||||||
|
// var rowForce = (isLeftSide ? 1 : -1) * config.RowForce(conn);
|
||||||
|
// force.X += rowForce;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return force;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
81
AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs
Normal file
81
AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//using System;
|
||||||
|
//using System.Threading;
|
||||||
|
//using System.Threading.Tasks;
|
||||||
|
//using System.Windows;
|
||||||
|
|
||||||
|
//namespace AIStudio.Wpf.DiagramDesigner.Layout
|
||||||
|
//{
|
||||||
|
// /// <summary>
|
||||||
|
// /// Reposition the nodes in a network using a physics-based approach.
|
||||||
|
// /// The nodes are interpreted as point masses, and the connections are represented
|
||||||
|
// /// by springs. This system, along with a few additional forces such as friction and a
|
||||||
|
// /// horizontal force, is then simulated to calculate the new position of the nodes.
|
||||||
|
// /// </summary>
|
||||||
|
// public class ForceDirectedLayouter
|
||||||
|
// {
|
||||||
|
// /// <summary>
|
||||||
|
// /// Layout the nodes in the network.
|
||||||
|
// /// </summary>
|
||||||
|
// /// <param name="config">The configuration to use.</param>
|
||||||
|
// /// <param name="maxIterations">The maximum amount of iterations after which the physics simulation ends.</param>
|
||||||
|
// public void Layout(Configuration config, int maxIterations)
|
||||||
|
// {
|
||||||
|
// var engine = new Engine();
|
||||||
|
// var state = new BufferedState();
|
||||||
|
|
||||||
|
// // Move each node so no two nodes have the exact same position.
|
||||||
|
// engine.ApplyRandomShift(config.Network);
|
||||||
|
|
||||||
|
// int deltaT = (int)Math.Ceiling(10.0 / (double)config.UpdatesPerIteration);
|
||||||
|
// for (int i = 0; i < maxIterations * config.UpdatesPerIteration; i++)
|
||||||
|
// {
|
||||||
|
// engine.Update(deltaT, state, config);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// foreach (var newNodePosition in state.NodePositions)
|
||||||
|
// {
|
||||||
|
// newNodePosition.Key.Position = new Point(newNodePosition.Value.X, newNodePosition.Value.Y);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Layout the nodes in the network, updating the user interface at each iteration.
|
||||||
|
// /// This method, contrary to Layout(), lets users see the simulation as it happens.
|
||||||
|
// /// The cancellation token should be used to end the simulation.
|
||||||
|
// /// </summary>
|
||||||
|
// /// <param name="config">The configuration to use.</param>
|
||||||
|
// /// <param name="token">A cancellation token to end the layout process.</param>
|
||||||
|
// /// <returns>The async task</returns>
|
||||||
|
// public async Task LayoutAsync(Configuration config, CancellationToken token)
|
||||||
|
// {
|
||||||
|
// var engine = new Engine();
|
||||||
|
// var state = new LiveState();
|
||||||
|
|
||||||
|
// // Move each node so no two nodes have the exact same position.
|
||||||
|
// engine.ApplyRandomShift(config.Network);
|
||||||
|
|
||||||
|
// DateTime start = DateTime.Now;
|
||||||
|
// TimeSpan t = TimeSpan.Zero;
|
||||||
|
// do
|
||||||
|
// {
|
||||||
|
// // Current real time
|
||||||
|
// var newT = DateTime.Now - start;
|
||||||
|
// var deltaT = newT - t;
|
||||||
|
|
||||||
|
// // Current modified time
|
||||||
|
// //int virtT = (int)(t.Milliseconds * Settings.TimeModifier);
|
||||||
|
// int virtDeltaT = (int)(deltaT.Milliseconds * config.TimeModifier);
|
||||||
|
// int virtDeltaTPerUpdate = virtDeltaT / config.UpdatesPerIteration;
|
||||||
|
// for (int i = 0; i < config.UpdatesPerIteration; i++)
|
||||||
|
// {
|
||||||
|
// // Modified time in this update step
|
||||||
|
// engine.Update(virtDeltaTPerUpdate, state, config);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// t = newT;
|
||||||
|
|
||||||
|
// await Task.Delay(14, token);
|
||||||
|
// } while (!token.IsCancellationRequested);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
100
AIStudio.Wpf.DiagramDesigner/Layout/State.cs
Normal file
100
AIStudio.Wpf.DiagramDesigner/Layout/State.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
//using System.Collections.Generic;
|
||||||
|
//using System.Windows;
|
||||||
|
//using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
||||||
|
|
||||||
|
//namespace AIStudio.Wpf.DiagramDesigner.Layout
|
||||||
|
//{
|
||||||
|
// internal interface IState
|
||||||
|
// {
|
||||||
|
// VectorBase GetNodePosition(DesignerItemViewModelBase node);
|
||||||
|
// void SetNodePosition(DesignerItemViewModelBase node, VectorBase pos);
|
||||||
|
// VectorBase GetEndpointPosition(ConnectorInfoBase endpoint);
|
||||||
|
// VectorBase GetNodeSpeed(DesignerItemViewModelBase node);
|
||||||
|
// void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// internal class BufferedState : IState
|
||||||
|
// {
|
||||||
|
// private readonly Dictionary<DesignerItemViewModelBase, VectorBase> _nodePositions = new Dictionary<DesignerItemViewModelBase, VectorBase>();
|
||||||
|
// private readonly Dictionary<ConnectorInfoBase, VectorBase> _endpointRelativePositions = new Dictionary<ConnectorInfoBase, VectorBase>();
|
||||||
|
// public IEnumerable<KeyValuePair<DesignerItemViewModelBase, VectorBase>> NodePositions => _nodePositions;
|
||||||
|
|
||||||
|
// private readonly Dictionary<DesignerItemViewModelBase, VectorBase> _nodeSpeeds = new Dictionary<DesignerItemViewModelBase, VectorBase>();
|
||||||
|
|
||||||
|
// public VectorBase GetNodePosition(DesignerItemViewModelBase node)
|
||||||
|
// {
|
||||||
|
// if (!_nodePositions.TryGetValue(node, out VectorBase result))
|
||||||
|
// {
|
||||||
|
// result = new VectorBase(node.Position.X, node.Position.Y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void SetNodePosition(DesignerItemViewModelBase node, VectorBase pos)
|
||||||
|
// {
|
||||||
|
// _nodePositions[node] = pos;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public VectorBase GetEndpointPosition(ConnectorInfoBase endpoint)
|
||||||
|
// {
|
||||||
|
// if (!_endpointRelativePositions.TryGetValue(endpoint, out VectorBase result))
|
||||||
|
// {
|
||||||
|
// result = new VectorBase(endpoint.MiddlePosition.X, endpoint.MiddlePosition.Y) - GetNodePosition(endpoint.Parent);
|
||||||
|
// _endpointRelativePositions[endpoint] = result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result + GetNodePosition(endpoint.Parent as DesignerItemViewModelBase);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public VectorBase GetNodeSpeed(DesignerItemViewModelBase node)
|
||||||
|
// {
|
||||||
|
// if (!_nodeSpeeds.TryGetValue(node, out VectorBase result))
|
||||||
|
// {
|
||||||
|
// result = new VectorBase(0, 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed)
|
||||||
|
// {
|
||||||
|
// _nodeSpeeds[node] = speed;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// internal class LiveState : IState
|
||||||
|
// {
|
||||||
|
// private readonly Dictionary<DesignerItemViewModelBase, VectorBase> _nodeSpeeds = new Dictionary<DesignerItemViewModelBase, VectorBase>();
|
||||||
|
|
||||||
|
// public VectorBase GetNodePosition(DesignerItemViewModelBase node)
|
||||||
|
// {
|
||||||
|
// return new VectorBase(node.Position.X, node.Position.Y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void SetNodePosition(DesignerItemViewModelBase node, VectorBase pos)
|
||||||
|
// {
|
||||||
|
// node.Position = new Point(pos.X, pos.Y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public VectorBase GetEndpointPosition(ConnectorInfoBase endpoint)
|
||||||
|
// {
|
||||||
|
// return new VectorBase(endpoint.MiddlePosition.X, endpoint.MiddlePosition.Y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public VectorBase GetNodeSpeed(DesignerItemViewModelBase node)
|
||||||
|
// {
|
||||||
|
// if (!_nodeSpeeds.TryGetValue(node, out VectorBase result))
|
||||||
|
// {
|
||||||
|
// result = new VectorBase(0, 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed)
|
||||||
|
// {
|
||||||
|
// _nodeSpeeds[node] = speed;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -6,6 +6,7 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using AIStudio.Wpf.DiagramDesigner.Controls;
|
||||||
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
||||||
using AIStudio.Wpf.DiagramDesigner.Models;
|
using AIStudio.Wpf.DiagramDesigner.Models;
|
||||||
|
|
||||||
@@ -442,7 +443,11 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new PointBase(Left, Top);
|
return TopLeft;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
TopLeft = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,6 +630,22 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<FullyCreatedConnectorInfo> InputConnectors
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Connectors.Where(p => p.Orientation == ConnectorOrientation.Left || p.Orientation == ConnectorOrientation.Top).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FullyCreatedConnectorInfo> OutputConnectors
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Connectors.Where(p => p.Orientation == ConnectorOrientation.Right || p.Orientation == ConnectorOrientation.Bottom).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected ObservableCollection<CinchMenuItem> menuOptions;
|
protected ObservableCollection<CinchMenuItem> menuOptions;
|
||||||
public IEnumerable<CinchMenuItem> MenuOptions
|
public IEnumerable<CinchMenuItem> MenuOptions
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user