From 6e917a21076da6334f64bbe20faccdcb8980bff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BE=E7=AB=B9?= Date: Thu, 21 Dec 2023 22:20:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=B8=83=E5=B1=80=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/Configuration.cs | 123 +++++----- AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs | 212 +++++++++--------- .../Layout/ForceDirectedLayouter.cs | 144 ++++++------ AIStudio.Wpf.DiagramDesigner/Layout/State.cs | 164 +++++++------- .../BaseViewModel/DiagramViewModel.cs | 17 ++ 5 files changed, 340 insertions(+), 320 deletions(-) diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs b/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs index c49336c..f27cdec 100644 --- a/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs +++ b/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs @@ -1,69 +1,72 @@ -//using System; +using System; -//namespace AIStudio.Wpf.DiagramDesigner.Layout -//{ -// public class Configuration -// { -// /// -// /// The network whose nodes are to be repositioned. -// /// -// public DiagramViewModel Network { get; set; } +namespace AIStudio.Wpf.DiagramDesigner.Layout +{ + public class Configuration + { + /// + /// The network whose nodes are to be repositioned. + /// + public DiagramViewModel Network + { + get; set; + } -// /// -// /// 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. -// /// -// public float TimeModifier { get; set; } = 3.5f; - -// /// -// /// Number of updates per iteration. -// /// Increasing this number increases the accuracy of the physics simulation at the cost of performance. -// /// -// public int UpdatesPerIteration { get; set; } = 1; + /// + /// 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. + /// + public float TimeModifier { get; set; } = 3.5f; -// /// -// /// How strongly should nodes push eachother away? -// /// A greater NodeRepulsionForce increases the distance between nodes. -// /// -// public float NodeRepulsionForce { get; set; } = 100; + /// + /// Number of updates per iteration. + /// Increasing this number increases the accuracy of the physics simulation at the cost of performance. + /// + public int UpdatesPerIteration { get; set; } = 1; -// /// -// /// 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. -// /// -// public Func EquilibriumDistance { get; set; } = conn => 100; + /// + /// How strongly should nodes push eachother away? + /// A greater NodeRepulsionForce increases the distance between nodes. + /// + public float NodeRepulsionForce { get; set; } = 100; -// /// -// /// A function that maps each connection onto the springiness/stiffness constant of its corresponding spring. -// /// (c.f. Hooke's law) -// /// -// public Func SpringConstant { get; set; } = conn => 1; + /// + /// 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. + /// + public Func EquilibriumDistance { get; set; } = conn => 100; -// /// -// /// 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. -// /// -// public Func RowForce { get; set; } = conn => 100; + /// + /// A function that maps each connection onto the springiness/stiffness constant of its corresponding spring. + /// (c.f. Hooke's law) + /// + public Func SpringConstant { get; set; } = conn => 1; -// /// -// /// A function that maps each node onto its mass in the physics simulation. -// /// Greater mass makes the node harder to move. -// /// -// public Func NodeMass { get; set; } = node => 10; + /// + /// 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. + /// + public Func RowForce { get; set; } = conn => 100; -// /// -// /// 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. -// /// -// public Func FrictionCoefficient { get; set; } = node => 2.5f; + /// + /// A function that maps each node onto its mass in the physics simulation. + /// Greater mass makes the node harder to move. + /// + public Func NodeMass { get; set; } = node => 10; -// /// -// /// A predicate function that specifies whether or not a node is fixed. -// /// Fixed nodes do not get moved in the simulation. -// /// -// public Func IsFixedNode { get; set; } = node => false; -// } -//} + /// + /// 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. + /// + public Func FrictionCoefficient { get; set; } = node => 2.5f; + + /// + /// A predicate function that specifies whether or not a node is fixed. + /// Fixed nodes do not get moved in the simulation. + /// + public Func IsFixedNode { get; set; } = node => false; + } +} diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs b/AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs index 9c0482a..c5b7c46 100644 --- a/AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs +++ b/AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs @@ -1,123 +1,123 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Windows; -//using AIStudio.Wpf.DiagramDesigner.Geometrys; +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()) -// { -// node.Position = node.Position + new VectorBase(random.NextDouble(), random.NextDouble()); -// } -// } +namespace AIStudio.Wpf.DiagramDesigner.Layout +{ + internal class Engine + { + internal void ApplyRandomShift(DiagramViewModel network) + { + Random random = new Random(); + foreach (var node in network.Items.OfType()) + { + 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); + 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()) -// { -// if (!config.IsFixedNode(node)) -// { -// nodeForces.Add((node, CalculateNodeForce(node, state, config))); -// } -// } + foreach (var node in config.Network.Items.OfType()) + { + 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)); -// } -// } + // 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(); + 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); -// } + // 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()) -// { -// if (node == otherNode) -// { -// continue; -// } + // 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()) + { + 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 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; -// } + 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; + // 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; -// } + return force; + } -// private VectorBase CalculateEndpointForce(ConnectorInfoBase endpoint, IState state, Configuration config) -// { -// var pos = state.GetEndpointPosition(endpoint); + private VectorBase CalculateEndpointForce(ConnectorInfoBase endpoint, IState state, Configuration config) + { + var pos = state.GetEndpointPosition(endpoint); -// VectorBase force = new VectorBase(); + 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; -// } + foreach (var conn in config.Network.Items.OfType().Where(p => p.SinkConnectorInfo == endpoint || p.SourceConnectorInfo == endpoint).ToList()) + { + var otherSide = conn.SourceConnectorInfo == endpoint ? conn.SinkConnectorInfo : conn.SourceConnectorInfo; + 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; + // 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; -// } + // 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; -// } -// } -//} + return force; + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs b/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs index cf23015..5e47dda 100644 --- a/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs +++ b/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs @@ -1,81 +1,81 @@ -//using System; -//using System.Threading; -//using System.Threading.Tasks; -//using System.Windows; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; -//namespace AIStudio.Wpf.DiagramDesigner.Layout -//{ -// /// -// /// 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. -// /// -// public class ForceDirectedLayouter -// { -// /// -// /// Layout the nodes in the network. -// /// -// /// The configuration to use. -// /// The maximum amount of iterations after which the physics simulation ends. -// public void Layout(Configuration config, int maxIterations) -// { -// var engine = new Engine(); -// var state = new BufferedState(); +namespace AIStudio.Wpf.DiagramDesigner.Layout +{ + /// + /// 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. + /// + public class ForceDirectedLayouter + { + /// + /// Layout the nodes in the network. + /// + /// The configuration to use. + /// The maximum amount of iterations after which the physics simulation ends. + 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); + // 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); -// } + 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); -// } -// } + foreach (var newNodePosition in state.NodePositions) + { + newNodePosition.Key.Position = new Point(newNodePosition.Value.X, newNodePosition.Value.Y); + } + } -// /// -// /// 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. -// /// -// /// The configuration to use. -// /// A cancellation token to end the layout process. -// /// The async task -// public async Task LayoutAsync(Configuration config, CancellationToken token) -// { -// var engine = new Engine(); -// var state = new LiveState(); + /// + /// 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. + /// + /// The configuration to use. + /// A cancellation token to end the layout process. + /// The async task + 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); + // 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); -// } + DateTime start = DateTime.Now; + TimeSpan t = TimeSpan.Zero; + do + { + // Current real time + var newT = DateTime.Now - start; + var deltaT = newT - t; -// t = newT; + // 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); + } -// await Task.Delay(14, token); -// } while (!token.IsCancellationRequested); -// } -// } -//} + t = newT; + + await Task.Delay(14, token); + } while (!token.IsCancellationRequested); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/State.cs b/AIStudio.Wpf.DiagramDesigner/Layout/State.cs index 5691571..707d909 100644 --- a/AIStudio.Wpf.DiagramDesigner/Layout/State.cs +++ b/AIStudio.Wpf.DiagramDesigner/Layout/State.cs @@ -1,100 +1,100 @@ -//using System.Collections.Generic; -//using System.Windows; -//using AIStudio.Wpf.DiagramDesigner.Geometrys; +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); -// } +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 _nodePositions = new Dictionary(); -// private readonly Dictionary _endpointRelativePositions = new Dictionary(); -// public IEnumerable> NodePositions => _nodePositions; + internal class BufferedState : IState + { + private readonly Dictionary _nodePositions = new Dictionary(); + private readonly Dictionary _endpointRelativePositions = new Dictionary(); + public IEnumerable> NodePositions => _nodePositions; -// private readonly Dictionary _nodeSpeeds = new Dictionary(); + private readonly Dictionary _nodeSpeeds = new Dictionary(); -// public VectorBase GetNodePosition(DesignerItemViewModelBase node) -// { -// if (!_nodePositions.TryGetValue(node, out VectorBase result)) -// { -// result = new VectorBase(node.Position.X, node.Position.Y); -// } + public VectorBase GetNodePosition(DesignerItemViewModelBase node) + { + if (!_nodePositions.TryGetValue(node, out VectorBase result)) + { + result = new VectorBase(node.Position.X, node.Position.Y); + } -// return result; -// } + return result; + } -// public void SetNodePosition(DesignerItemViewModelBase node, VectorBase pos) -// { -// _nodePositions[node] = pos; -// } + 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; -// } + public VectorBase GetEndpointPosition(ConnectorInfoBase endpoint) + { + if (!_endpointRelativePositions.TryGetValue(endpoint, out VectorBase result)) + { + result = new VectorBase(endpoint.MiddlePosition.X, endpoint.MiddlePosition.Y) - GetNodePosition(endpoint.Parent as DesignerItemViewModelBase); + _endpointRelativePositions[endpoint] = result; + } -// return result + GetNodePosition(endpoint.Parent as DesignerItemViewModelBase); -// } + return result + GetNodePosition(endpoint.Parent as DesignerItemViewModelBase); + } -// public VectorBase GetNodeSpeed(DesignerItemViewModelBase node) -// { -// if (!_nodeSpeeds.TryGetValue(node, out VectorBase result)) -// { -// result = new VectorBase(0, 0); -// } + public VectorBase GetNodeSpeed(DesignerItemViewModelBase node) + { + if (!_nodeSpeeds.TryGetValue(node, out VectorBase result)) + { + result = new VectorBase(0, 0); + } -// return result; -// } + return result; + } -// public void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed) -// { -// _nodeSpeeds[node] = speed; -// } -// } + public void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed) + { + _nodeSpeeds[node] = speed; + } + } -// internal class LiveState : IState -// { -// private readonly Dictionary _nodeSpeeds = new Dictionary(); + internal class LiveState : IState + { + private readonly Dictionary _nodeSpeeds = new Dictionary(); -// public VectorBase GetNodePosition(DesignerItemViewModelBase node) -// { -// return new VectorBase(node.Position.X, node.Position.Y); -// } + 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 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 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); -// } + public VectorBase GetNodeSpeed(DesignerItemViewModelBase node) + { + if (!_nodeSpeeds.TryGetValue(node, out VectorBase result)) + { + result = new VectorBase(0, 0); + } -// return result; -// } + return result; + } -// public void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed) -// { -// _nodeSpeeds[node] = speed; -// } -// } -//} + public void SetNodeSpeed(DesignerItemViewModelBase node, VectorBase speed) + { + _nodeSpeeds[node] = speed; + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs index de24da7..7cf3831 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs @@ -10,6 +10,7 @@ using System.Windows.Input; using System.Windows.Media; using AIStudio.Wpf.DiagramDesigner.Geometrys; using AIStudio.Wpf.DiagramDesigner.Helpers; +using AIStudio.Wpf.DiagramDesigner.Layout; using AIStudio.Wpf.DiagramDesigner.Models; using Newtonsoft.Json; using static System.Net.Mime.MediaTypeNames; @@ -676,6 +677,16 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private ICommand _autoLayoutCommand; + public ICommand AutoLayoutCommand + { + get + { + return this._autoLayoutCommand ?? (this._autoLayoutCommand = new SimpleCommand(ExecuteEnable, ExecuteAutoLayoutCommand)); + } + } + + private ICommand _groupCommand; public ICommand GroupCommand { @@ -2438,6 +2449,12 @@ namespace AIStudio.Wpf.DiagramDesigner FitViewModel = new FitViewModel() { BoundingRect = DiagramViewModelHelper.GetBoundingRectangle(Items.OfType()), FitMode = FitMode.FitHeight }; } } + + private void ExecuteAutoLayoutCommand(object parameter) + { + ForceDirectedLayouter layouter = new ForceDirectedLayouter(); + layouter.Layout(new Configuration { Network = this }, 10000); + } #endregion #region 分组