diff --git a/AIStudio.Wpf.DiagramApp/Views/MainWindow.xaml b/AIStudio.Wpf.DiagramApp/Views/MainWindow.xaml index 9a51c94..5aa58b8 100644 --- a/AIStudio.Wpf.DiagramApp/Views/MainWindow.xaml +++ b/AIStudio.Wpf.DiagramApp/Views/MainWindow.xaml @@ -2531,11 +2531,6 @@ - - - - - diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs b/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs deleted file mode 100644 index f27cdec..0000000 --- a/AIStudio.Wpf.DiagramDesigner/Layout/Configuration.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; - -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; - - /// - /// 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 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 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 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 node onto its mass in the physics simulation. - /// Greater mass makes the node harder to move. - /// - public Func NodeMass { get; set; } = node => 10; - - /// - /// 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 deleted file mode 100644 index c5b7c46..0000000 --- a/AIStudio.Wpf.DiagramDesigner/Layout/Engine.cs +++ /dev/null @@ -1,123 +0,0 @@ -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()); - } - } - - 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))); - } - } - - // 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()) - { - 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 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; - - // 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; - } - } -} diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs b/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs deleted file mode 100644 index 5e47dda..0000000 --- a/AIStudio.Wpf.DiagramDesigner/Layout/ForceDirectedLayouter.cs +++ /dev/null @@ -1,81 +0,0 @@ -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(); - - // 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); - } - } - - /// - /// 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); - - 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); - } - } -} diff --git a/AIStudio.Wpf.DiagramDesigner/Layout/State.cs b/AIStudio.Wpf.DiagramDesigner/Layout/State.cs deleted file mode 100644 index 707d909..0000000 --- a/AIStudio.Wpf.DiagramDesigner/Layout/State.cs +++ /dev/null @@ -1,100 +0,0 @@ -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 _nodePositions = new Dictionary(); - private readonly Dictionary _endpointRelativePositions = new Dictionary(); - public IEnumerable> NodePositions => _nodePositions; - - 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); - } - - 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 as DesignerItemViewModelBase); - _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 _nodeSpeeds = new Dictionary(); - - 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; - } - } -} diff --git a/AIStudio.Wpf.DiagramDesigner/Styles/ToggleButton.xaml b/AIStudio.Wpf.DiagramDesigner/Styles/ToggleButton.xaml new file mode 100644 index 0000000..9ab98f4 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner/Styles/ToggleButton.xaml @@ -0,0 +1,96 @@ + + + + \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner/Themes/Style.xaml b/AIStudio.Wpf.DiagramDesigner/Themes/Style.xaml index 76d04a9..1998cd9 100644 --- a/AIStudio.Wpf.DiagramDesigner/Themes/Style.xaml +++ b/AIStudio.Wpf.DiagramDesigner/Themes/Style.xaml @@ -9,5 +9,6 @@ + \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs index a6b9a5e..d5ed9eb 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs @@ -10,10 +10,8 @@ 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; namespace AIStudio.Wpf.DiagramDesigner { @@ -680,17 +678,7 @@ namespace AIStudio.Wpf.DiagramDesigner { return this._fitHeightCommand ?? (this._fitHeightCommand = new SimpleCommand(ExecuteEnable, ExecuteFitHeightCommand)); } - } - - private ICommand _autoLayoutCommand; - public ICommand AutoLayoutCommand - { - get - { - return this._autoLayoutCommand ?? (this._autoLayoutCommand = new SimpleCommand(ExecuteEnable, ExecuteAutoLayoutCommand)); - } - } - + } private ICommand _groupCommand; public ICommand GroupCommand @@ -2462,12 +2450,6 @@ 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 分组