Parallel edge modifications for connection points (#77)

* Modified the grouping mechanism used for parallel edges. This is in preparation for some other changes related to the way edges with connection points are handled. These code changes will alter the sequence of edges and does not attempt to preserve the exact ordering of the old code. In the old code, the groups were built simply as the edges were encountered and did not appear to have a defined order, so I think this should be fine, but it could change the appearance of existing graphs. The new code also excludes edges that have both ends tied to connection points because those will not be spaced within the group anyway. There is a small performance penalty for very small numbers of edges, but at 50-100 edges a 3-10x speed improvement can be observed.

* Modified the parallel edge logic so that it takes connection points into account. If an edge uses a connection point at only one end and parallel line grouping is in effect, the edge will be rendered parallel, but aligned to the connection point.

* Restored the use of parallel edge groups with just one edge. This way, parallel edge behavior is applied to single edges with one connection point.

* Edge Drag (#75)

* + SetEdgesDrag

* + Edge가 Drag 가능하도록 설정

* + Edge Drag

* Fix

* + PrepareEdgePathFromMousePointer Target Position Edit
This commit is contained in:
Jon 2017-05-21 04:47:33 -07:00 коммит произвёл Alexander Smirnov
Родитель 5bb92f961c
Коммит 11d1ddd1c4
5 изменённых файлов: 225 добавлений и 176 удалений

Просмотреть файл

@ -242,9 +242,18 @@ namespace GraphX.Controls
protected EdgeControlBase() protected EdgeControlBase()
{ {
_updateLabelPosition = true; _updateLabelPosition = true;
Loaded += EdgeControlBase_Loaded;
} }
private bool _updateLabelPosition; private bool _isInDesignMode = false;
private void EdgeControlBase_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= EdgeControlBase_Loaded;
_isInDesignMode = CustomHelper.IsInDesignMode(this);
}
private bool _updateLabelPosition;
/// <summary> /// <summary>
/// Gets or sets if label position should be updated on edge update /// Gets or sets if label position should be updated on edge update
@ -587,7 +596,7 @@ namespace GraphX.Controls
} }
internal int ParallelEdgeOffset; internal int ParallelEdgeOffset;
//internal int TargetOffset; //internal int TargetOffset;
/// <summary> /// <summary>
/// Gets the offset point for edge parallelization /// Gets the offset point for edge parallelization
@ -595,23 +604,20 @@ namespace GraphX.Controls
/// <param name="source">Source vertex</param> /// <param name="source">Source vertex</param>
/// <param name="target">Target vertex</param> /// <param name="target">Target vertex</param>
/// <param name="sideDistance">Distance between edges</param> /// <param name="sideDistance">Distance between edges</param>
internal virtual Point GetParallelOffset(VertexControl source, VertexControl target, int sideDistance) internal virtual Point GetParallelOffset(Point sourceCenter, Point targetCenter, int sideDistance)
{ {
var sourcepos = source.GetPosition(); var mainVector = new Vector(targetCenter.X - sourceCenter.X, targetCenter.Y - sourceCenter.Y);
var targetpos = target.GetPosition();
var mainVector = new Vector(targetpos.X - sourcepos.X, targetpos.Y - sourcepos.Y);
//get new point coordinate //get new point coordinate
var joint = new Point( var joint = new Point(
sourcepos.X + source.DesiredSize.Width * .5 + sideDistance * (mainVector.Y / mainVector.Length), sourceCenter.X + sideDistance * (mainVector.Y / mainVector.Length),
sourcepos.Y + source.DesiredSize.Height * .5 - sideDistance * (mainVector.X / mainVector.Length)); sourceCenter.Y - sideDistance * (mainVector.X / mainVector.Length));
return joint; return joint;
} }
/// <summary> /// <summary>
/// Internal value to store last calculated Source vertex connection point /// Internal value to store last calculated Source vertex connection point
/// </summary> /// </summary>
protected internal Point? SourceConnectionPoint; protected internal Point? SourceConnectionPoint;
/// <summary> /// <summary>
/// Internal value to store last calculated Target vertex connection point /// Internal value to store last calculated Target vertex connection point
@ -892,37 +898,42 @@ namespace GraphX.Controls
if ((Visibility != Visibility.Visible && !IsHiddenEdgesUpdated) && this.Source == null || this.Target == null || ManualDrawing || !IsTemplateLoaded) return; if ((Visibility != Visibility.Visible && !IsHiddenEdgesUpdated) && this.Source == null || this.Target == null || ManualDrawing || !IsTemplateLoaded) return;
#region Get the inputs #region Get the inputs
// Get the TopLeft position of the Source Vertex.
var sourceTopLeft = new Point(
(useCurrentCoords ? GraphAreaBase.GetX(Source) : GraphAreaBase.GetFinalX(Source)),
(useCurrentCoords ? GraphAreaBase.GetY(Source) : GraphAreaBase.GetFinalY(Source)));
// Get the TopLeft position of the Target Vertex.
var targetTopLeft = new Point(
(useCurrentCoords ? GraphAreaBase.GetX(Target) : GraphAreaBase.GetFinalX(Target)),
(useCurrentCoords ? GraphAreaBase.GetY(Target) : GraphAreaBase.GetFinalY(Target)));
//get the size of the source //get the size of the source
var sourceSize = new Size Size sourceSize;
{ if (_isInDesignMode)
Width = this.Source.ActualWidth, sourceSize = new Size(80, 20);
Height = this.Source.ActualHeight else
}; sourceSize = new Size(Source.ActualWidth, Source.ActualHeight);
if (CustomHelper.IsInDesignMode(this)) sourceSize = new Size(80, 20);
//get the position center of the source
var sourcePos = new Point
{
X = (useCurrentCoords ? GraphAreaBase.GetX(this.Source) : GraphAreaBase.GetFinalX(this.Source)) + sourceSize.Width * .5,
Y = (useCurrentCoords ? GraphAreaBase.GetY(this.Source) : GraphAreaBase.GetFinalY(this.Source)) + sourceSize.Height * .5
};
//get the size of the target //get the size of the target
var targetSize = new Size Size targetSize;
{ if (_isInDesignMode)
Width = this.Target.ActualWidth,
Height = this.Target.ActualHeight
};
if (CustomHelper.IsInDesignMode(this))
targetSize = new Size(80, 20); targetSize = new Size(80, 20);
else
targetSize = new Size(Target.ActualWidth, Target.ActualHeight);
//get the position center of the source
var sourceCenter = new Point(
sourceTopLeft.X + sourceSize.Width * .5,
sourceTopLeft.Y + sourceSize.Height * .5);
//get the position center of the target //get the position center of the target
var targetPos = new Point
{ var targetCenter = new Point(
X = (useCurrentCoords ? GraphAreaBase.GetX(this.Target) : GraphAreaBase.GetFinalX(this.Target)) + targetSize.Width * .5, targetTopLeft.X + targetSize.Width * .5,
Y = (useCurrentCoords ? GraphAreaBase.GetY(this.Target) : GraphAreaBase.GetFinalY(this.Target)) + targetSize.Height * .5 targetTopLeft.Y + targetSize.Height * .5);
};
var routedEdge = Edge as IRoutingInfo; var routedEdge = Edge as IRoutingInfo;
if (routedEdge == null) if (routedEdge == null)
@ -931,19 +942,6 @@ namespace GraphX.Controls
//get the route informations //get the route informations
var routeInformation = externalRoutingPoints ?? routedEdge.RoutingPoints; var routeInformation = externalRoutingPoints ?? routedEdge.RoutingPoints;
// Get the TopLeft position of the Source Vertex.
var sourcePos1 = new Point
{
X = (useCurrentCoords ? GraphAreaBase.GetX(this.Source) : GraphAreaBase.GetFinalX(this.Source)),
Y = (useCurrentCoords ? GraphAreaBase.GetY(this.Source) : GraphAreaBase.GetFinalY(this.Source))
};
// Get the TopLeft position of the Target Vertex.
var targetPos1 = new Point
{
X = (useCurrentCoords ? GraphAreaBase.GetX(this.Target) : GraphAreaBase.GetFinalX(this.Target)),
Y = (useCurrentCoords ? GraphAreaBase.GetY(this.Target) : GraphAreaBase.GetFinalY(this.Target))
};
var hasEpSource = EdgePointerForSource != null; var hasEpSource = EdgePointerForSource != null;
var hasEpTarget = EdgePointerForTarget != null; var hasEpTarget = EdgePointerForTarget != null;
@ -952,59 +950,114 @@ namespace GraphX.Controls
//if self looped edge //if self looped edge
if (IsSelfLooped) if (IsSelfLooped)
{ {
PrepareSelfLoopedEdge(sourcePos1); PrepareSelfLoopedEdge(sourceTopLeft);
return; return;
} }
//check if we have some edge route data //check if we have some edge route data
var hasRouteInfo = routeInformation != null && routeInformation.Length > 1; var hasRouteInfo = routeInformation != null && routeInformation.Length > 1;
//calculate source and target edge attach points
if (RootArea != null && !hasRouteInfo && RootArea.EnableParallelEdges && ParallelEdgeOffset != 0)
{
sourcePos = GetParallelOffset(this.Source, this.Target, ParallelEdgeOffset);
targetPos = GetParallelOffset(this.Target, this.Source, -ParallelEdgeOffset);
}
/* Rectangular shapes implementation by bleibold */
var gEdge = Edge as IGraphXCommonEdge; var gEdge = Edge as IGraphXCommonEdge;
Point p1;
Point p2; #region Helper lambda expressions
Func<IVertexConnectionPoint> getSourceCpOrThrow = () =>
{
var cp = Source.GetConnectionPointById(gEdge.SourceConnectionPointId.Value, true);
if (cp == null)
throw new GX_ObjectNotFoundException(string.Format("Can't find source vertex VCP by edge source connection point Id({1}) : {0}", Source, gEdge.SourceConnectionPointId));
return cp;
};
Func<IVertexConnectionPoint> getTargetCpOrThrow = () =>
{
var cp = Target.GetConnectionPointById(gEdge.TargetConnectionPointId.Value, true);
if (cp == null)
throw new GX_ObjectNotFoundException(string.Format("Can't find target vertex VCP by edge target connection point Id({1}) : {0}", Target, gEdge.TargetConnectionPointId));
return cp;
};
Func<IVertexConnectionPoint, Point, Point, Point> getCpEndPoint = (cp, cpCenter, distantEnd) =>
{
// If the connection point (cp) doesn't have any shape, the edge comes from its center, otherwise find the location
// on its perimeter that the edge should come from.
Point calculatedCp;
if (cp.Shape == VertexShape.None)
calculatedCp = cpCenter;
else
calculatedCp = GeometryHelper.GetEdgeEndpoint(cpCenter, cp.RectangularSize, distantEnd, cp.Shape);
return calculatedCp;
};
Func<bool> needParallelCalc = () => RootArea != null && !hasRouteInfo && RootArea.EnableParallelEdges && IsParallel;
#endregion
//calculate edge source (p1) and target (p2) endpoints based on different settings //calculate edge source (p1) and target (p2) endpoints based on different settings
if (gEdge?.SourceConnectionPointId != null) if (gEdge?.SourceConnectionPointId != null && gEdge?.TargetConnectionPointId != null)
{ {
var sourceCp = this.Source.GetConnectionPointById(gEdge.SourceConnectionPointId.Value, true); // Get the connection points and their centers
if (sourceCp == null) var sourceCp = getSourceCpOrThrow();
throw new GX_ObjectNotFoundException(string.Format("Can't find source vertex VCP by edge source connection point Id({1}) : {0}", this.Source, gEdge.SourceConnectionPointId)); var targetCp = getTargetCpOrThrow();
if (sourceCp.Shape == VertexShape.None) p1 = sourceCp.RectangularSize.Center(); var sourceCpCenter = sourceCp.RectangularSize.Center();
else var targetCpCenter = targetCp.RectangularSize.Center();
SourceConnectionPoint = getCpEndPoint(sourceCp, sourceCpCenter, targetCpCenter);
TargetConnectionPoint = getCpEndPoint(targetCp, targetCpCenter, sourceCpCenter);
}
else if (gEdge?.SourceConnectionPointId != null)
{
var sourceCp = getSourceCpOrThrow();
var sourceCpCenter = sourceCp.RectangularSize.Center();
// In the case of parallel edges, the target direction needs to be found and the correct offset calculated. Otherwise, fall back
// to route information or simply the center of the target vertex.
if (needParallelCalc())
{ {
var targetCpPos = gEdge.TargetConnectionPointId.HasValue ? this.Target.GetConnectionPointById(gEdge.TargetConnectionPointId.Value, true).RectangularSize.Center() : (hasRouteInfo ? routeInformation[1].ToWindows() : (targetPos)); targetCenter = sourceCpCenter + (targetCenter - sourceCenter);
p1 = GeometryHelper.GetEdgeEndpoint(sourceCp.RectangularSize.Center(), sourceCp.RectangularSize, targetCpPos, sourceCp.Shape);
} }
else if (hasRouteInfo)
{
targetCenter = routeInformation[1].ToWindows();
}
SourceConnectionPoint = getCpEndPoint(sourceCp, sourceCpCenter, targetCenter);
TargetConnectionPoint = GeometryHelper.GetEdgeEndpoint(targetCenter, new SysRect(targetTopLeft, targetSize), hasRouteInfo ? routeInformation[routeInformation.Length - 2].ToWindows() : sourceCpCenter, Target.VertexShape);
}
else if (gEdge?.TargetConnectionPointId != null)
{
var targetCp = getTargetCpOrThrow();
var targetCpCenter = targetCp.RectangularSize.Center();
// In the case of parallel edges, the source direction needs to be found and the correct offset calculated. Otherwise, fall back
// to route information or simply the center of the source vertex.
if (needParallelCalc())
{
sourceCenter = targetCpCenter + (sourceCenter - targetCenter);
}
else if (hasRouteInfo)
{
sourceCenter = routeInformation[routeInformation.Length - 2].ToWindows();
}
SourceConnectionPoint = GeometryHelper.GetEdgeEndpoint(sourceCenter, new SysRect(sourceTopLeft, sourceSize), (hasRouteInfo ? routeInformation[1].ToWindows() : targetCpCenter), Source.VertexShape);
TargetConnectionPoint = getCpEndPoint(targetCp, targetCpCenter, sourceCenter);
} }
else else
p1 = GeometryHelper.GetEdgeEndpoint(sourcePos, new SysRect(sourcePos1, sourceSize), (hasRouteInfo ? routeInformation[1].ToWindows() : (targetPos)), this.Source.VertexShape);
if (gEdge?.TargetConnectionPointId != null)
{ {
var targetCp = this.Target.GetConnectionPointById(gEdge.TargetConnectionPointId.Value, true); //calculate source and target edge attach points
if (targetCp == null) if (needParallelCalc())
throw new GX_ObjectNotFoundException(string.Format("Can't find target vertex VCP by edge target connection point Id({1}) : {0}", this.Target, gEdge.TargetConnectionPointId));
if (targetCp.Shape == VertexShape.None) p2 = targetCp.RectangularSize.Center();
else
{ {
var sourceCpPos = gEdge.SourceConnectionPointId.HasValue ? this.Source.GetConnectionPointById(gEdge.SourceConnectionPointId.Value, true).RectangularSize.Center() : hasRouteInfo ? routeInformation[routeInformation.Length - 2].ToWindows() : (sourcePos); var origSC = sourceCenter;
p2 = GeometryHelper.GetEdgeEndpoint(targetCp.RectangularSize.Center(), targetCp.RectangularSize, sourceCpPos, targetCp.Shape); var origTC = targetCenter;
sourceCenter = GetParallelOffset(origSC, origTC, ParallelEdgeOffset);
targetCenter = GetParallelOffset(origTC, origSC, -ParallelEdgeOffset);
} }
SourceConnectionPoint = GeometryHelper.GetEdgeEndpoint(sourceCenter, new SysRect(sourceTopLeft, sourceSize), (hasRouteInfo ? routeInformation[1].ToWindows() : (targetCenter)), Source.VertexShape);
TargetConnectionPoint = GeometryHelper.GetEdgeEndpoint(targetCenter, new SysRect(targetTopLeft, targetSize), hasRouteInfo ? routeInformation[routeInformation.Length - 2].ToWindows() : (sourceCenter), Target.VertexShape);
} }
else
p2 = GeometryHelper.GetEdgeEndpoint(targetPos, new SysRect(targetPos1, targetSize), hasRouteInfo ? routeInformation[routeInformation.Length - 2].ToWindows() : (sourcePos), this.Target.VertexShape);
SourceConnectionPoint = p1; // If the logic above is working correctly, both the source and target connection points will exist.
TargetConnectionPoint = p2; if (!SourceConnectionPoint.HasValue || !TargetConnectionPoint.HasValue)
throw new GX_GeneralException("One or both connection points was not found due to an internal error.");
var p1 = SourceConnectionPoint.Value;
var p2 = TargetConnectionPoint.Value;
Linegeometry = new PathGeometry(); Linegeometry = new PathGeometry();
PathFigure lineFigure; PathFigure lineFigure;
@ -1095,7 +1148,7 @@ namespace GraphX.Controls
EdgeLabelControl.UpdatePosition(); EdgeLabelControl.UpdatePosition();
} }
private Point UpdateSourceEpData(Point from, Point to, bool allowUnsuppress = true) private Point UpdateSourceEpData(Point from, Point to, bool allowUnsuppress = true)
{ {
var dir = MathHelper.GetDirection(from, to); var dir = MathHelper.GetDirection(from, to);
if (from == to) if (from == to)

Просмотреть файл

@ -1546,12 +1546,16 @@ namespace GraphX.Controls
//setup path //setup path
if (_svShowEdgeLabels == true) if (_svShowEdgeLabels == true)
edgectrl.SetCurrentValue(EdgeControlBase.ShowLabelProperty, true); edgectrl.SetCurrentValue(EdgeControlBase.ShowLabelProperty, true);
edgectrl.PrepareEdgePath();
} }
if (LogicCore.EnableParallelEdges) if (LogicCore.EnableParallelEdges)
UpdateParallelEdgesData(); UpdateParallelEdgesData();
foreach (var item in _edgeslist)
{
item.Value.PrepareEdgePath();
}
GenerateEdgeLabels(); GenerateEdgeLabels();
} }
@ -1574,78 +1578,52 @@ namespace GraphX.Controls
public virtual void UpdateParallelEdgesData(Dictionary<TEdge, EdgeControl> edgeList = null) public virtual void UpdateParallelEdgesData(Dictionary<TEdge, EdgeControl> edgeList = null)
{ {
edgeList = edgeList ?? _edgeslist; edgeList = edgeList ?? _edgeslist;
var usedIds = edgeList.Count > 20 ? new HashSet<long>() as ICollection<long> : new List<long>();
//clear IsParallel flag //clear IsParallel flag
edgeList.Values.ForEach(a => a.IsParallel = false); edgeList.Values.ForEach(a => a.IsParallel = false);
foreach (var item in edgeList) // Group edges together that share the same source and target. Edges that have both a source and target connection point defined are excluded. Self
// looped edges are excluded. Edges marked with CanBeParallel == false are excluded. Edges with a connection point are pushed to the end of the group
// and will be marked as parallel, but their offsets end up overridden during rendering.
var edgeGroups =
(from edge in edgeList
where edge.Value.CanBeParallel && !edge.Key.IsSelfLoop && (!edge.Key.SourceConnectionPointId.HasValue || !edge.Key.TargetConnectionPointId.HasValue)
group edge by new Tuple<long, long>(Math.Min(edge.Key.Source.ID, edge.Key.Target.ID), Math.Max(edge.Key.Source.ID, edge.Key.Target.ID)) into edgeGroup
select edgeGroup.OrderBy(e => e.Key.SourceConnectionPointId.HasValue || e.Key.TargetConnectionPointId.HasValue ? 1 : 0).ToList())
.ToList();
foreach (var list in edgeGroups)
{ {
if (usedIds.Contains(item.Key.ID) || !item.Value.CanBeParallel) continue; var first = list[0];
var list = new List<EdgeControl> { item.Value };
//that list will contain checks for edges that goes form target to source // Alternate sides with each step
var cList = new List<bool> { false }; int viceversa = 1;
foreach (var edge in edgeList) // Check if total number of edges without connection points is even or not
bool even = (list.TakeWhile(e => !e.Key.SourceConnectionPointId.HasValue && !e.Key.TargetConnectionPointId.HasValue).Count() % 2) == 0;
// For even numbers of edges, initial offset is a half step from the center
int initialOffset = even ? LogicCore.ParallelEdgeDistance / 2 : 0;
for (int i = 0; i < list.Count; i++)
{ {
//skip the same edge var kvp = list[i];
if (item.Key.ID == edge.Key.ID) continue; kvp.Value.IsParallel = true;
//add source to target edge
if (edge.Value.CanBeParallel && ((item.Key.Source.ID == edge.Key.Source.ID && item.Key.Target.ID == edge.Key.Target.ID))) var offset = viceversa * (initialOffset + LogicCore.ParallelEdgeDistance * ((i + (even ? 0 : 1)) / 2));
//if source to target edge
if (kvp.Key.Source == first.Key.Source)
{ {
list.Add(edge.Value); kvp.Value.ParallelEdgeOffset = offset;
cList.Add(false);
} }
//add target to source edge and remember the check else //if target to source edge - just switch offsets
if (item.Key.Source.ID == edge.Key.Target.ID && item.Key.Target.ID == edge.Key.Source.ID)
{ {
cList.Add(true); kvp.Value.ParallelEdgeOffset = -offset;
list.Add(edge.Value);
} }
//else cList.Add(false); //change trigger to opposite
viceversa = -viceversa;
} }
//do stuff
if (list.Count > 1)
{
//trigger to show in which side to step distance
bool viceversa = false;
//check if total number of edges is even or not
bool even = (list.Count % 2) == 0;
//get the resulting step distance for the case
int distance = even ? (int)(LogicCore.ParallelEdgeDistance * .5) : LogicCore.ParallelEdgeDistance;
//leave first edge intact if we have not even edges count
for (int i = even ? 0 : 1; i < list.Count; i++)
{
//if source to target edge
if (!cList[i])
{
list[i].ParallelEdgeOffset = (viceversa ? -distance : distance) * (1 + ((even ? i : i - 1) / 2));
//list[i].TargetOffset = -list[i].ParallelEdgeOffset;
}
else //if target to source edge - just switch offsets
{
list[i].ParallelEdgeOffset = -((viceversa ? -distance : distance) * (1 + ((even ? i : i - 1) / 2)));
//list[i].ParallelEdgeOffset = -list[i].TargetOffset;
}
//change trigger to opposite
viceversa = !viceversa;
list[i].IsParallel = true;
}
}
//remember used edges IDs
list.ForEach(a =>
{
var edge = a.Edge as TEdge;
if (edge != null) usedIds.Add(edge.ID);
});
list.Clear();
} }
} }
#endregion #endregion
#region GenerateEdgesForVertex() #region GenerateEdgesForVertex()

Просмотреть файл

@ -117,7 +117,7 @@ namespace GraphX.Controls
var position = TranslatePoint(new Point(), VertexControl); var position = TranslatePoint(new Point(), VertexControl);
var vPos = VertexControl.GetPosition(); var vPos = VertexControl.GetPosition();
position = new Point(position.X + vPos.X, position.Y + vPos.Y); position = new Point(position.X + vPos.X, position.Y + vPos.Y);
RectangularSize = new Rect(position, DesiredSize); RectangularSize = new Rect(position, new Size(ActualWidth, ActualHeight));
} }
#elif METRO #elif METRO

Просмотреть файл

@ -200,7 +200,9 @@ namespace GraphX.Controls
/// <param name="round"></param> /// <param name="round"></param>
public Point GetPosition(bool final = false, bool round = false) public Point GetPosition(bool final = false, bool round = false)
{ {
return round ? new Point(final ? (int)GraphAreaBase.GetFinalX(this) : (int)GraphAreaBase.GetX(this), final ? (int)GraphAreaBase.GetFinalY(this) : (int)GraphAreaBase.GetY(this)) : new Point(final ? GraphAreaBase.GetFinalX(this) : GraphAreaBase.GetX(this), final ? GraphAreaBase.GetFinalY(this) : GraphAreaBase.GetY(this)); return round ?
new Point(final ? (int)GraphAreaBase.GetFinalX(this) : (int)GraphAreaBase.GetX(this), final ? (int)GraphAreaBase.GetFinalY(this) : (int)GraphAreaBase.GetY(this)) :
new Point(final ? GraphAreaBase.GetFinalX(this) : GraphAreaBase.GetX(this), final ? GraphAreaBase.GetFinalY(this) : GraphAreaBase.GetY(this));
} }
/// <summary> /// <summary>
/// Get control position on the GraphArea panel in attached coords X and Y (GraphX type version) /// Get control position on the GraphArea panel in attached coords X and Y (GraphX type version)

Просмотреть файл

@ -33,28 +33,29 @@ namespace GraphX.Controls
/// <returns></returns> /// <returns></returns>
public static Vector? Intersects(Vector a1, Vector a2, Vector b1, Vector b2) public static Vector? Intersects(Vector a1, Vector a2, Vector b1, Vector b2)
{ {
var b = a2 - a1; var a = a2 - a1;
var d = b2 - b1; var b = b2 - b1;
var bDotDPerp = b.X * d.Y - b.Y * d.X; var aDotBPerp = a.X * b.Y - a.Y * b.X;
// if b dot d == 0, it means the lines are parallel so have infinite intersection points // if a dot b == 0, it means the lines are parallel so have infinite intersection points
if (bDotDPerp == 0) if (aDotBPerp == 0)
return null; return null;
var c = b1 - a1; var c = b1 - a1;
var t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
if (t < 0 || t > 1)
{
return null;
}
var u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; // The intersection must fall within the line segment defined by the b1 and b2 endpoints.
var u = (c.X * a.Y - c.Y * a.X) / aDotBPerp;
if (u < 0 || u > 1) if (u < 0 || u > 1)
{ {
return null; return null;
} }
return a1 + t * b; // The intersection point IS allowed to fall outside of the line segment defined by the a1 and a2
// endpoints, anywhere along the infinite line. When this is used to find the intersection of an
// Edge as line a and Vertex side as line b, it allows the Edge to be elongated to the intersection.
var t = (c.X * b.Y - c.Y * b.X) / aDotBPerp;
return a1 + t * a;
} }
/// <summary> /// <summary>
@ -376,30 +377,45 @@ namespace GraphX.Controls
public static Point GetEdgeEndpointOnRectangle(Point sourcePos, Rect sourceBounds, Point targetPos, double angle = 0) public static Point GetEdgeEndpointOnRectangle(Point sourcePos, Rect sourceBounds, Point targetPos, double angle = 0)
{ {
var tgt_pt = targetPos; Func<Point, double, Point> rotate = (p, a) => angle == 0.0 ? p : MathHelper.RotatePoint(p, sourceBounds.Center(), a);
if (angle != 0)
tgt_pt = MathHelper.RotatePoint(targetPos, sourceBounds.Center(), -angle);
var leftSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.TopLeft().ToVector(), sourceBounds.BottomLeft().ToVector()); var tgt_pt = rotate(targetPos, -angle);
var bottomSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.BottomLeft().ToVector(), sourceBounds.BottomRight().ToVector());
var rightSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.TopRight().ToVector(), sourceBounds.BottomRight().ToVector());
var topSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.TopLeft().ToVector(), sourceBounds.TopRight().ToVector());
var pt = new Point(sourcePos.X, sourcePos.Y); if (tgt_pt.X <= sourcePos.X)
{
var leftSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.TopLeft().ToVector(), sourceBounds.BottomLeft().ToVector());
if (leftSide.HasValue)
{
return rotate(new Point(leftSide.Value.X, leftSide.Value.Y), angle);
}
}
else
{
var rightSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.TopRight().ToVector(), sourceBounds.BottomRight().ToVector());
if (rightSide.HasValue)
{
return rotate(new Point(rightSide.Value.X, rightSide.Value.Y), angle);
}
}
// Get the rectangle side where intersection of the proposed Edge path occurred. if (tgt_pt.Y <= sourcePos.Y)
if (leftSide != null) {
pt = new Point(leftSide.Value.X, leftSide.Value.Y); var topSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.TopLeft().ToVector(), sourceBounds.TopRight().ToVector());
else if (bottomSide != null) if (topSide.HasValue)
pt = new Point(bottomSide.Value.X, bottomSide.Value.Y); {
else if (rightSide != null) return rotate(new Point(topSide.Value.X, topSide.Value.Y), angle);
pt = new Point(rightSide.Value.X, rightSide.Value.Y); }
else if (topSide != null) }
pt = new Point(topSide.Value.X, topSide.Value.Y); else
{
var bottomSide = Intersects(sourcePos.ToVector(), tgt_pt.ToVector(), sourceBounds.BottomLeft().ToVector(), sourceBounds.BottomRight().ToVector());
if (bottomSide.HasValue)
{
return rotate(new Point(bottomSide.Value.X, bottomSide.Value.Y), angle);
}
}
if ((leftSide != null || bottomSide != null || rightSide != null || topSide != null) && angle != 0) return rotate(new Point(sourcePos.X, sourcePos.Y), angle);
pt = MathHelper.RotatePoint(pt, sourceBounds.Center(), angle);
return pt;
} }
public static PathFigure GenerateOldArrow(Point ip1, Point ip2) public static PathFigure GenerateOldArrow(Point ip1, Point ip2)