Skip to content

Commit

Permalink
Replace OngoingPosition with a PositionAnchor, Link.Target is now nev…
Browse files Browse the repository at this point in the history
…er null and Factory signature changed
  • Loading branch information
zHaytam committed Sep 16, 2022
1 parent b109146 commit 6a071ef
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 173 deletions.
163 changes: 82 additions & 81 deletions docs/Layouts/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@using Blazor.Diagrams.Core
@using Blazor.Diagrams.Core.Geometry
@using Blazor.Diagrams.Core.Models
@using Blazor.Diagrams.Core.Models.Base
@using QG = QuikGraph

<!-- required to resolve DiagramCanvas component -->
Expand All @@ -22,105 +23,105 @@ or it will not be rendered.
100vh = 100% viewport height
-->
<div style="width:100vw; height: 70vh">
<CascadingValue Value="_diagram">
<DiagramCanvas></DiagramCanvas>
</CascadingValue>
<CascadingValue Value="_diagram">
<DiagramCanvas></DiagramCanvas>
</CascadingValue>
</div>

<div>
<MatSelect Label="Layout chart" @bind-Value="@_layout" Style="width: 125px">
@foreach (var algo in new GraphShape.Algorithms.Layout.StandardLayoutAlgorithmFactory<string, QG.IEdge<string>, QG.IBidirectionalGraph<string, QG.IEdge<string>>>().AlgorithmTypes)
{
<MatOption TValue="string" Value="@algo">@algo</MatOption>
}
</MatSelect>

<MatButton Icon="grid_on" @onclick="@(_ => OnLayout(_layout))" Raised="true">Go!</MatButton>
<MatSelect Label="Layout chart" @bind-Value="@_layout" Style="width: 125px">
@foreach (var algo in new GraphShape.Algorithms.Layout.StandardLayoutAlgorithmFactory<string, QG.IEdge<string>, QG.IBidirectionalGraph<string, QG.IEdge<string>>>().AlgorithmTypes)
{
<MatOption TValue="string" Value="@algo">@algo</MatOption>
}
</MatSelect>

<MatButton Icon="grid_on" @onclick="@(_ => OnLayout(_layout))" Raised="true">Go!</MatButton>
</div>

@code {
private Diagram _diagram { get; set; }
private Diagram _diagram { get; set; }

private string _layout;
private string _layout;

protected override void OnInitialized()
{
base.OnInitialized();

var options = new BlazorDiagramOptions
protected override void OnInitialized()
{
AllowMultiSelection = true,
Zoom =
base.OnInitialized();

var options = new BlazorDiagramOptions
{
AllowMultiSelection = true,
Zoom =
{
Minimum = 0.5
} // Whether to allow multi selection using CTRL
};
_diagram = new BlazorDiagram(options);

Setup();
}

private void Setup()
{
var node1 = NewNode(50, 50);
var node2 = NewNode(300, 300);
var node3 = NewNode(300, 50);
_diagram.Nodes.Add(new[] { node1, node2, node3 });

// use portless nodes so connection points can move around after layout
_diagram.Links.Add(new LinkModel(node1, node2));
}

private static NodeModel NewNode(double x, double y)
{
var node = new NodeModel(Guid.NewGuid().ToString(), new Point(x, y));
return node;
}

private void OnLayout(string layout)
{
if (string.IsNullOrWhiteSpace(layout))
};
_diagram = new BlazorDiagram(options);

Setup();
}

private void Setup()
{
return;
var node1 = NewNode(50, 50);
var node2 = NewNode(300, 300);
var node3 = NewNode(300, 50);
_diagram.Nodes.Add(new[] { node1, node2, node3 });

// use portless nodes so connection points can move around after layout
_diagram.Links.Add(new LinkModel(node1, node2));
}

// convert Z.Blazor.Diagram to QuikGraph
var graph = new QG.BidirectionalGraph<NodeModel, QG.Edge<NodeModel>>();
var nodes = _diagram.Nodes.OfType<NodeModel>().ToList();
var edges = _diagram.Links.OfType<LinkModel>()
.Select(lm =>
{
var source = nodes.Single(dn => dn.Id == lm.Source.Model.Id);
var target = nodes.Single(dn => dn.Id == lm?.Target?.Model?.Id);
return new QG.Edge<NodeModel>(source, target);
})
.ToList();
graph.AddVertexRange(nodes);
graph.AddEdgeRange(edges);

// run GraphShape algorithm
var positions = nodes.ToDictionary(nm => nm, dn => new GraphShape.Point(dn.Position.X, dn.Position.Y));
var sizes = nodes.ToDictionary(nm => nm, dn => new GraphShape.Size(dn.Size?.Width ?? 100, dn.Size?.Height ?? 100));
var layoutCtx = new LayoutContext<NodeModel, QG.Edge<NodeModel>, QG.BidirectionalGraph<NodeModel, QG.Edge<NodeModel>>>(graph, positions, sizes, LayoutMode.Simple);
var algoFact = new StandardLayoutAlgorithmFactory<NodeModel, QG.Edge<NodeModel>, QG.BidirectionalGraph<NodeModel, QG.Edge<NodeModel>>>();
var algo = algoFact.CreateAlgorithm(layout, layoutCtx, null);

algo.Compute();

// update NodeModel positions
try
private static NodeModel NewNode(double x, double y)
{
_diagram.SuspendRefresh = true;
foreach (var vertPos in algo.VerticesPositions)
{
// NOTE; have to use SetPosition which takes care of updating everything
vertPos.Key.SetPosition(vertPos.Value.X, vertPos.Value.Y);
}
var node = new NodeModel(Guid.NewGuid().ToString(), new Point(x, y));
return node;
}
finally

private void OnLayout(string layout)
{
_diagram.SuspendRefresh = false;
if (string.IsNullOrWhiteSpace(layout))
{
return;
}

// convert Z.Blazor.Diagram to QuikGraph
var graph = new QG.BidirectionalGraph<NodeModel, QG.Edge<NodeModel>>();
var nodes = _diagram.Nodes.OfType<NodeModel>().ToList();
var edges = _diagram.Links.OfType<LinkModel>()
.Select(lm =>
{
var source = nodes.Single(dn => dn.Id == (lm.Source.Model as Model).Id);
var target = nodes.Single(dn => dn.Id == (lm.Target.Model as Model)?.Id);
return new QG.Edge<NodeModel>(source, target);
})
.ToList();
graph.AddVertexRange(nodes);
graph.AddEdgeRange(edges);

// run GraphShape algorithm
var positions = nodes.ToDictionary(nm => nm, dn => new GraphShape.Point(dn.Position.X, dn.Position.Y));
var sizes = nodes.ToDictionary(nm => nm, dn => new GraphShape.Size(dn.Size?.Width ?? 100, dn.Size?.Height ?? 100));
var layoutCtx = new LayoutContext<NodeModel, QG.Edge<NodeModel>, QG.BidirectionalGraph<NodeModel, QG.Edge<NodeModel>>>(graph, positions, sizes, LayoutMode.Simple);
var algoFact = new StandardLayoutAlgorithmFactory<NodeModel, QG.Edge<NodeModel>, QG.BidirectionalGraph<NodeModel, QG.Edge<NodeModel>>>();
var algo = algoFact.CreateAlgorithm(layout, layoutCtx, null);

algo.Compute();

// update NodeModel positions
try
{
_diagram.SuspendRefresh = true;
foreach (var vertPos in algo.VerticesPositions)
{
// NOTE; have to use SetPosition which takes care of updating everything
vertPos.Key.SetPosition(vertPos.Value.X, vertPos.Value.Y);
}
}
finally
{
_diagram.SuspendRefresh = false;
}
}
}

}
6 changes: 3 additions & 3 deletions samples/SharedDemo/Demos/BotAnswerWidget.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<input type="text" class="form-control"
@bind-value="Node.Answer"
placeholder="Answer"
@onmousedown:stopPropagation
@onmouseup:stopPropagation
@onmousemove:stopPropagation />
@onpointerdown:stopPropagation
@onpointerup:stopPropagation
@onpointermove:stopPropagation />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Console.WriteLine(Link.Source);
Console.WriteLine(Link.Target);
var sourcePosition = Link.Source.GetPosition(Link);
var targetPosition = Link.Target is null ? Link.OnGoingPosition : Link.Target.GetPosition(Link);
var targetPosition = Link.Target.GetPosition(Link);

if (sourcePosition is null || targetPosition is null)
return;
Expand Down
6 changes: 3 additions & 3 deletions samples/SharedDemo/Demos/DynamicInsertions.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ protected override void OnInitialized()
BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 50)));
BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 400)));

BlazorDiagram.Options.Links.Factory = (d, sp) =>
BlazorDiagram.Options.Links.Factory = (d, s, ta) =>
{
var link = new LinkModel(new SinglePortAnchor(sp)
var link = new LinkModel(new SinglePortAnchor(s as PortModel)
{
UseShapeAndAlignment = false
})
}, ta)
{
SourceMarker = LinkMarker.Arrow
};
Expand Down
3 changes: 0 additions & 3 deletions src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ public static void ReconnectLinksToClosestPorts(this Diagram diagram)

foreach (var link in diagram.Links.ToArray())
{
if (link.Target == null)
continue;

if (link.Source is not SinglePortAnchor spa1 || link.Target is not SinglePortAnchor spa2)
continue;

Expand Down
21 changes: 21 additions & 0 deletions src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Blazor.Diagrams.Core.Geometry;
using Blazor.Diagrams.Core.Models.Base;

namespace Blazor.Diagrams.Core.Anchors
{
public class PositionAnchor : Anchor
{
private Point _position;

public PositionAnchor(Point position) : base(null)
{
_position = position;
}

public void SetPosition(Point position) => _position = position;

public override Point? GetPlainPosition() => _position;

public override Point? GetPosition(BaseLinkModel link, Point[] route) => _position;
}
}
40 changes: 19 additions & 21 deletions src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Blazor.Diagrams.Core.Models;
using Blazor.Diagrams.Core.Models;
using Blazor.Diagrams.Core.Models.Base;
using Blazor.Diagrams.Core.Events;
using System.Linq;
Expand All @@ -10,6 +9,7 @@ namespace Blazor.Diagrams.Core.Behaviors
public class DragNewLinkBehavior : Behavior
{
private BaseLinkModel? _ongoingLink;
private PositionAnchor? _targetPositionAnchor;

public DragNewLinkBehavior(Diagram diagram) : base(diagram)
{
Expand All @@ -18,22 +18,16 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram)
Diagram.PointerUp += OnPointerUp;
}

public void StartFrom(Anchor source, double clientX, double clientY)
public void StartFrom(ILinkable source, double clientX, double clientY)
{
if (_ongoingLink != null)
return;

//_ongoingLink = Diagram.Options.Links.Factory(Diagram, port);
StartFrom(new LinkModel(source), clientX, clientY);
}

public void StartFrom(BaseLinkModel link, double clientX, double clientY)
{
if (_ongoingLink != null)
_targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5));
_ongoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor);
if (_ongoingLink == null)
return;

_ongoingLink = link;
_ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5);
Diagram.Links.Add(_ongoingLink);
}

Expand All @@ -42,16 +36,20 @@ private void OnPointerDown(Model? model, MouseEventArgs e)
if (e.Button != (int)MouseEventButton.Left)
return;

_ongoingLink = null;
_targetPositionAnchor = null;

if (model is PortModel port)
{
if (port.Locked)
return;

_ongoingLink = Diagram.Options.Links.Factory(Diagram, port);
_targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5));
_ongoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor);
if (_ongoingLink == null)
return;

_ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5);
_ongoingLink.SetTarget(_targetPositionAnchor);
Diagram.Links.Add(_ongoingLink);
}
}
Expand All @@ -61,14 +59,14 @@ private void OnPointerMove(Model? model, MouseEventArgs e)
if (_ongoingLink == null || model != null)
return;

_ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5);
_targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5));

if (Diagram.Options.Links.EnableSnapping)
{
var nearPort = FindNearPortToAttachTo();
if (nearPort != null || _ongoingLink.Target != null)
if (nearPort != null || _ongoingLink.Target is not PositionAnchor)
{
_ongoingLink.SetTarget(nearPort is null ? null : new SinglePortAnchor(nearPort));
_ongoingLink.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort));
}
}

Expand All @@ -86,10 +84,9 @@ private void OnPointerUp(Model? model, MouseEventArgs e)
return;
}

if (model is ILinkable linkable && _ongoingLink.Source.Model.CanAttachTo(linkable))
if (model is ILinkable linkable && (_ongoingLink.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(linkable)))
{
var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable);
_ongoingLink.OnGoingPosition = null;
_ongoingLink.SetTarget(targetAnchor);
_ongoingLink.Refresh();
}
Expand All @@ -103,10 +100,11 @@ private void OnPointerUp(Model? model, MouseEventArgs e)

private PortModel? FindNearPortToAttachTo()
{
var ongoingPosition = _targetPositionAnchor!.GetPosition(_ongoingLink!)!;
foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports))
{
if (_ongoingLink!.OnGoingPosition!.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius
&& _ongoingLink.Source.Model.CanAttachTo(port))
if (ongoingPosition.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius
&& (_ongoingLink!.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(port)))
{
return port;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve
if (behavior == null)
throw new DiagramsException($"DragNewLinkBehavior was not found");

// Todo: use factory from options
behavior.StartFrom(new ShapeIntersectionAnchor(node), e.ClientX, e.ClientY);
behavior.StartFrom(node, e.ClientX, e.ClientY);
return ValueTask.CompletedTask;
}
}
2 changes: 1 addition & 1 deletion src/Blazor.Diagrams.Core/Delegates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Blazor.Diagrams.Core

public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target);

public delegate BaseLinkModel? LinkFactory(Diagram diagram, PortModel sourcePort);
public delegate BaseLinkModel? LinkFactory(Diagram diagram, ILinkable source, Anchor targetAnchor);

public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model);

Expand Down
Loading

0 comments on commit 6a071ef

Please sign in to comment.