+ Added vertex control labels with following functionality:

+ 2 available positioning modes: by sides or by coordinates
	+ Always attached to vertex and moves with it
	+ Same template logic as in EdgeLabelControl
+ Added key modifiers to vertex event args
+ Fixed ViewFinder not deriving ZoomControl background
+ Implemented some properties as dependencies
+ Reworked EdgeLabelControl inner logic. Should now be more flexible and performance efficient.
This commit is contained in:
panthernet 2014-02-26 18:43:24 +04:00
Родитель 8a9f321e15
Коммит f3966d8042
19 изменённых файлов: 472 добавлений и 120 удалений

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

@ -1,6 +1,14 @@
RELEASE 2.0.2
+ Added vertex control labels with following functionality:
+ 2 available positioning modes: by sides or by coordinates
+ Always attached to vertex and moves with it
+ Same template logic as in EdgeLabelControl
+ Added key modifiers to vertex event args
+ Fixed labels rendering for parallel edges. Now they are displayed separately for each edge.
+ Fixed async calculations being broken due to LogicCore property became dependency
+ Fixed ViewFinder not deriving ZoomControl background
+ Implemented some properties as dependencies
+ Reworked EdgeLabelControl inner logic. Should now be more flexible and performance efficient.
+ Some code refactoring. Minor performance improvements.
RELEASE 2.0.1

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

@ -5,6 +5,7 @@ using System.Linq;
using System.Windows;
using GraphX.GraphSharp.Algorithms.EdgeRouting;
using GraphX.GraphSharp.Algorithms.Layout.Simple.FDP;
using GraphX.GraphSharp.Algorithms.OverlapRemoval;
using GraphX.Logic;
using GraphX.GraphSharp.Algorithms.Layout.Simple.Tree;
using GraphX.GraphSharp.Algorithms.Layout;
@ -28,52 +29,44 @@ namespace ShowcaseExample
tst_Area.SetVerticesDrag(true, true);
}
private GraphExample GenerateTestGraph()
{
var graph = new GraphExample();
var v1 = new DataVertex() { Text = "Test1", ID = 1 };
graph.AddVertex(v1);
var v2 = new DataVertex() { Text = "Test2", ID = 2 };
graph.AddVertex(v2);
var v3 = new DataVertex() { Text = "Test3", ID = 3 };
graph.AddVertex(v3);
var v4 = new DataVertex() { Text = "Test4", ID = 4 };
graph.AddVertex(v4);
graph.AddEdge(new DataEdge(v1, v2, 100));
graph.AddEdge(new DataEdge(v2, v3, 100));
graph.AddEdge(new DataEdge(v2, v4, 100));
return graph;
}
void tst_but_gen_Click(object sender, RoutedEventArgs e)
{
var _graph = new GraphExample();
var v1 = new DataVertex() { Text = "Test1", ID = 1 };
_graph.AddVertex(v1);
var v2 = new DataVertex() { Text = "Test2", ID = 2 };
_graph.AddVertex(v2);
var v3 = new DataVertex() { Text = "Test3", ID = 3 };
_graph.AddVertex(v3);
var v4 = new DataVertex() { Text = "Test4", ID = 4 };
_graph.AddVertex(v4);
_graph.AddEdge(new DataEdge(v1, v2, 1) { ID = 1000 });
_graph.AddEdge(new DataEdge(v1, v2, 1) { ID = 1 });
_graph.AddEdge(new DataEdge(v1, v4, 1) { ID = 1000 });
_graph.AddEdge(new DataEdge(v1, v4, 1) { ID = 1 });
_graph.AddEdge(new DataEdge(v1, v4, 1) { ID = 2 });
_graph.AddEdge(new DataEdge(v2, v4, 1) { ID = 1001 });
_graph.AddEdge(new DataEdge(v3, v4, 1) { ID = 1002 });
_graph.AddEdge(new DataEdge(v3, v4, 1) { ID = 1003 });
_graph.AddEdge(new DataEdge(v4, v3, 1) { ID = 1004 });
_graph.AddEdge(new DataEdge(v4, v3, 1) { ID = 1005 });
_graph.AddEdge(new DataEdge(v4, v3, 1) { ID = 1006 });
tst_Area.ShowAllEdgesArrows(true);
var ergTreeLayoutParameters = new KKLayoutParameters { };
var logic = new LogicCoreExample();
TSTLC = logic;
logic.Graph = _graph;
logic.EnableParallelEdges = true;
var graph = GenerateTestGraph();
var logic = new LogicCoreExample {Graph = graph};
logic.EnableParallelEdges = false;
logic.ParallelEdgeDistance = 15;
logic.DefaultLayoutAlgorithm = LayoutAlgorithmTypeEnum.KK;
logic.DefaultLayoutAlgorithmParams = ergTreeLayoutParameters;
tst_Area.ShowAllEdgesArrows(false);
var layParams = new LinLogLayoutParameters { IterationCount = 100 };
logic.DefaultLayoutAlgorithm = LayoutAlgorithmTypeEnum.LinLog;
logic.DefaultLayoutAlgorithmParams = layParams;
var overlapParams = new OverlapRemovalParameters { HorizontalGap = 100, VerticalGap = 100 };
logic.DefaultOverlapRemovalAlgorithm = OverlapRemovalAlgorithmTypeEnum.FSA;
logic.DefaultOverlapRemovalAlgorithmParams = logic.AlgorithmFactory.CreateOverlapRemovalParameters(OverlapRemovalAlgorithmTypeEnum.FSA);
logic.DefaultOverlapRemovalAlgorithmParams = overlapParams;
IExternalEdgeRouting<DataVertex, DataEdge> erParams = null;
//logic.ExternalEdgeRoutingAlgorithm =
((GraphX.GraphSharp.Algorithms.OverlapRemoval.OverlapRemovalParameters)logic.DefaultOverlapRemovalAlgorithmParams).HorizontalGap = 140;
((GraphX.GraphSharp.Algorithms.OverlapRemoval.OverlapRemovalParameters)logic.DefaultOverlapRemovalAlgorithmParams).VerticalGap = 140;
tst_Area.GenerateGraph(_graph, true);
tst_Area.GenerateGraph(graph, true);
//tst_Area.VertexList[v1].Visibility = System.Windows.Visibility.Collapsed;
//tst_Area.VertexList[v2].Visibility = System.Windows.Visibility.Collapsed;
//tst_Area.VertexList[v3].Visibility = System.Windows.Visibility.Collapsed;

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

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace ShowcaseExample.Models

Двоичные данные
GraphX v2.v11.suo

Двоичный файл не отображается.

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

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace GraphX
{
@ -13,42 +10,72 @@ namespace GraphX
private const int BOT = 4; /* двоичное 0100 */
private const int TOP = 8; /* двоичное 1000 */
const double D30_DEGREES_IN_RADIANS = Math.PI / 6.0;
public static double Tangent30Degrees { get; private set; }
static MathHelper()
{
Double d30DegreesInRadians = Math.PI / 6.0;
Tangent30Degrees = Math.Tan(d30DegreesInRadians);
Tangent30Degrees = Math.Tan(D30_DEGREES_IN_RADIANS);
}
public static double GetAngleBetweenPoints(Point point1, Point point2)
{
return Math.Atan2(point1.Y - point2.Y, point2.X - point1.X);
}
public static double GetDistanceBetweenPoints(Point point1, Point point2)
{
return Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2));
}
public static Point RotatePoint(Point pointToRotate, Point centerPoint, double angleInDegrees)
{
var angleInRadians = angleInDegrees * (Math.PI / 180);
var cosTheta = Math.Cos(angleInRadians);
var sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
(cosTheta * (pointToRotate.X - centerPoint.X) -
sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
Y =
(int)
(sinTheta * (pointToRotate.X - centerPoint.X) +
cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
};
}
public static bool IsIntersected(Rect r, Point a, Point b)
{
sides code;
Point c; /* одна из точек */
Point start = new Point(a.X, a.Y);
// var start = new Point(a.X, a.Y);
/* код конечных точек отрезка */
sides code_a = GetIntersectionData(r, a);
sides code_b = GetIntersectionData(r, b);
var codeA = GetIntersectionData(r, a);
var codeB = GetIntersectionData(r, b);
if (code_a.IsInside() && code_b.IsInside())
if (codeA.IsInside() && codeB.IsInside())
return true;
/* пока одна из точек отрезка вне прямоугольника */
while (!code_a.IsInside() || !code_b.IsInside())
while (!codeA.IsInside() || !codeB.IsInside())
{
/* если обе точки с одной стороны прямоугольника, то отрезок не пересекает прямоугольник */
if (code_a.SameSide(code_b))
if (codeA.SameSide(codeB))
return false;
/* выбираем точку c с ненулевым кодом */
if (!code_a.IsInside())
sides code;
Point c; /* одна из точек */
if (!codeA.IsInside())
{
code = code_a;
code = codeA;
c = a;
}
else
{
code = code_b;
code = codeB;
c = b;
}
@ -77,15 +104,15 @@ namespace GraphX
}
/* обновляем код */
if (code == code_a)
if (code == codeA)
{
a = c;
code_a = GetIntersectionData(r, a);
codeA = GetIntersectionData(r, a);
}
else
{
b = c;
code_b = GetIntersectionData(r, b);
codeB = GetIntersectionData(r, b);
}
}
return true;
@ -95,10 +122,10 @@ namespace GraphX
{
sides code;
Point c; /* одна из точек */
Point start = new Point(a.X, a.Y);
var start = new Point(a.X, a.Y);
/* код конечных точек отрезка */
sides code_a = GetIntersectionData(r, a);
sides code_b = GetIntersectionData(r, b);
var code_a = GetIntersectionData(r, a);
var code_b = GetIntersectionData(r, b);
/* пока одна из точек отрезка вне прямоугольника */
while (!code_a.IsInside() || !code_b.IsInside())

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

@ -28,7 +28,7 @@ namespace GraphX
bool EnableParallelEdges { get; set; }
int ParallelEdgeDistance { get; set; }
bool IsEdgeRoutingEnabled { get; }
bool EnableEdgeLabelsOverlapRemoval { get; set; }
void CreateNewAlgorithmFactory();
void CreateNewAlgorithmStorage(IExternalLayout<TVertex> layout, IExternalOverlapRemoval<TVertex> or, IExternalEdgeRouting<TVertex, TEdge> er);
@ -40,5 +40,7 @@ namespace GraphX
IExternalLayout<TVertex> GenerateLayoutAlgorithm(Dictionary<TVertex, Size> vertexSizes);
IExternalOverlapRemoval<TVertex> GenerateOverlapRemovalAlgorithm(Dictionary<TVertex, Rect> rectangles = null);
IExternalEdgeRouting<TVertex, TEdge> GenerateEdgeRoutingAlgorithm(Size DesiredSize);
}
}

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

@ -24,5 +24,6 @@ namespace GraphX
IOverlapRemovalParameters CreateOverlapRemovalParameters(OverlapRemovalAlgorithmTypeEnum algorithmType);
IExternalEdgeRouting<TVertex, TEdge> CreateEdgeRoutingAlgorithm(EdgeRoutingAlgorithmTypeEnum newAlgorithmType, Rect graphArea, TGraph Graph, IDictionary<TVertex, Point> Positions, IDictionary<TVertex, Rect> Rectangles, IEdgeRoutingParameters parameters = null);
IEdgeRoutingParameters CreateEdgeRoutingParameters(EdgeRoutingAlgorithmTypeEnum algorithmType);
IOverlapRemovalAlgorithm<T> CreateFSAA<T>(IDictionary<T, Rect> rectangles, float horgap, float vertGap) where T : class;
}
}

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

@ -93,7 +93,7 @@ namespace GraphX
ec.StrokeDashArray = null;
break;
}
ec.UpdateEdge();
ec.UpdateEdge(false);
}
private DoubleCollection StrokeDashArray { get; set; }
@ -115,6 +115,12 @@ namespace GraphX
/// </summary>
public bool CanBeParallel { get { return _canbeparallel; } set { _canbeparallel = value; } }
private bool _updateLabelPosition;
/// <summary>
/// Gets or sets if label position should be updated on edge update
/// </summary>
public bool UpdateLabelPosition { get { return _updateLabelPosition; } set { _updateLabelPosition = true; } }
/// <summary>
/// Gets if this edge is self looped (have same Source and Target)
/// </summary>
@ -126,14 +132,26 @@ namespace GraphX
/// <summary>
/// Show arrows on the edge ends. Default value is true.
/// </summary>
public bool ShowArrows { get { return _showarrows; } set { _showarrows = value; UpdateEdge(); } }
public bool ShowArrows { get { return _showarrows; } set { _showarrows = value; UpdateEdge(false); } }
private bool _showarrows;
public static readonly DependencyProperty ShowLabelProperty = DependencyProperty.Register("ShowLabel",
typeof(bool),
typeof(EdgeControl),
new UIPropertyMetadata(showlabel_changed));
private static void showlabel_changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ec = (d as EdgeControl);
if(ec == null) return;
ec.UpdateEdge(false);
}
/// <summary>
/// Show edge label.Default value is False.
/// </summary>
public bool ShowLabel { get { return _showlabel; } set { _showlabel = value; UpdateEdge(); } }
private bool _showlabel;
public bool ShowLabel { get { return (bool)GetValue(ShowLabelProperty); } set { SetValue(ShowLabelProperty, value); } }
/// <summary>
/// Gets or sets if lables should be aligned to edges and be displayed under the same angle
@ -250,6 +268,7 @@ namespace GraphX
Edge = edge; DataContext = edge;
ShowArrows = showArrows;
ShowLabel = showLabels;
_updateLabelPosition = true;
EventOptions = new EdgeEventOptions(this);
foreach (var item in Enum.GetValues(typeof(EventType)).Cast<EventType>())
@ -480,11 +499,11 @@ namespace GraphX
#region public PrepareEdgePath()
internal void UpdateEdge()
internal void UpdateEdge(bool updateLabel = true)
{
if (Visibility == Visibility.Visible && _linePathObject != null)
{
PrepareEdgePath(true);
PrepareEdgePath(true, null, updateLabel);
_linePathObject.Data = _linegeometry;
_linePathObject.StrokeDashArray = StrokeDashArray;
@ -522,7 +541,7 @@ namespace GraphX
/// </summary>
/// <param name="useCurrentCoords">Use current vertices coordinates or final coorfinates (for.ex if move animation is active final coords will be its destination)</param>
/// <param name="externalRoutingPoints">Provided custom routing points will be used instead of stored ones.</param>
public void PrepareEdgePath(bool useCurrentCoords = false, Point[] externalRoutingPoints = null)
public void PrepareEdgePath(bool useCurrentCoords = false, Point[] externalRoutingPoints = null, bool updateLabel = true)
{
//do not calculate invisible edges
if (Visibility != Visibility.Visible || Source == null || Target == null || ManualDrawing) return;
@ -661,7 +680,9 @@ namespace GraphX
}
GeometryHelper.TryFreeze(_linegeometry);
GeometryHelper.TryFreeze(_arrowgeometry);
if (ShowLabel && _edgeLabelControl != null && _updateLabelPosition && updateLabel )
_edgeLabelControl.UpdatePosition();
}
else
{
@ -675,5 +696,26 @@ namespace GraphX
{
Clean();
}
public Rect GetLabelSize()
{
return _edgeLabelControl.LastKnownRectSize;
}
public void SetCustomLabelSize(Rect rect)
{
_edgeLabelControl.LastKnownRectSize = rect;
_edgeLabelControl.Arrange(rect);
}
internal void UpdateLabelLayout()
{
_edgeLabelControl.Visibility = Visibility.Visible;
if (_edgeLabelControl.LastKnownRectSize == Rect.Empty || double.IsNaN(_edgeLabelControl.Width))
{
_edgeLabelControl.UpdateLayout();
_edgeLabelControl.UpdatePosition();
}
}
}
}

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

@ -1,5 +1,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@ -30,11 +32,26 @@ namespace GraphX
public EdgeLabelControl()
{
if (DesignerProperties.GetIsInDesignMode(this)) return;
LayoutUpdated += EdgeLabelControl_LayoutUpdated;
HorizontalAlignment = HorizontalAlignment.Left;
VerticalAlignment = VerticalAlignment.Top;
}
void EdgeLabelControl_LayoutUpdated(object sender, EventArgs e)
{
//TODO optimize parent call by calling it once from constructor
var edgeControl = GetEdgeControl(VisualParent);
if(edgeControl == null || !edgeControl.ShowLabel) return;
if (LastKnownRectSize == Rect.Empty || double.IsNaN(LastKnownRectSize.Width) || LastKnownRectSize.Width == 0)
{
UpdateLayout();
UpdatePosition();
}
else Arrange(LastKnownRectSize);
}
private static EdgeControl GetEdgeControl(DependencyObject parent)
{
while (parent != null)
@ -46,24 +63,17 @@ namespace GraphX
return null;
}
private static double GetAngleBetweenPoints(Point point1, Point point2)
{
return Math.Atan2(point1.Y - point2.Y, point2.X - point1.X);
}
private static double GetDistanceBetweenPoints(Point point1, Point point2)
{
return Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2));
}
private static double GetLabelDistance(double edgeLength)
{
return edgeLength / 2; // set the label halfway the length of the edge
}
private void EdgeLabelControl_LayoutUpdated(object sender, EventArgs e)
internal void UpdatePosition()
{
if (double.IsNaN(DesiredSize.Width) || DesiredSize.Width == 0) return;
// if (!IsLoaded)
// return;
var edgeControl = GetEdgeControl(VisualParent);
@ -91,25 +101,27 @@ namespace GraphX
var routingInfo = edgeControl.Edge as IRoutingInfo;
if (routingInfo != null)
{
var routePoints = routingInfo.RoutingPoints;
var routePoints = routingInfo.RoutingPoints == null ? null : routingInfo.RoutingPoints.ToArray();
if (routePoints == null)
{
// the edge is a single segment (p1,p2)
edgeLength = GetLabelDistance(GetDistanceBetweenPoints(p1, p2));
edgeLength = GetLabelDistance(MathHelper.GetDistanceBetweenPoints(p1, p2));
}
else
{
// the edge has one or more segments
// compute the total length of all the segments
edgeLength = 0;
var rplen = routePoints.Length;
for (var i = 0; i <= rplen; ++i)
if (i == 0)
edgeLength += GetDistanceBetweenPoints(p1, routePoints[0]);
edgeLength += MathHelper.GetDistanceBetweenPoints(p1, routePoints[0]);
else if (i == rplen)
edgeLength += GetDistanceBetweenPoints(routePoints[rplen - 1], p2);
edgeLength += MathHelper.GetDistanceBetweenPoints(routePoints[rplen - 1], p2);
else
edgeLength += GetDistanceBetweenPoints(routePoints[i - 1], routePoints[i]);
edgeLength += MathHelper.GetDistanceBetweenPoints(routePoints[i - 1], routePoints[i]);
// find the line segment where the half distance is located
edgeLength = GetLabelDistance(edgeLength);
var newp1 = p1;
@ -118,11 +130,11 @@ namespace GraphX
{
double lengthOfSegment;
if (i == 0)
lengthOfSegment = GetDistanceBetweenPoints(newp1 = p1, newp2 = routePoints[0]);
lengthOfSegment = MathHelper.GetDistanceBetweenPoints(newp1 = p1, newp2 = routePoints[0]);
else if (i == rplen)
lengthOfSegment = GetDistanceBetweenPoints(newp1 = routePoints[rplen - 1], newp2 = p2);
lengthOfSegment = MathHelper.GetDistanceBetweenPoints(newp1 = routePoints[rplen - 1], newp2 = p2);
else
lengthOfSegment = GetDistanceBetweenPoints(newp1 = routePoints[i - 1], newp2 = routePoints[i]);
lengthOfSegment = MathHelper.GetDistanceBetweenPoints(newp1 = routePoints[i - 1], newp2 = routePoints[i]);
if (lengthOfSegment >= edgeLength)
break;
edgeLength -= lengthOfSegment;
@ -139,43 +151,29 @@ namespace GraphX
// move it "edgLength" on the segment
double tmpAngle;
var angleBetweenPoints = tmpAngle = GetAngleBetweenPoints(p1, p2);
var angleBetweenPoints = tmpAngle = MathHelper.GetAngleBetweenPoints(p1, p2);
//set angle in degrees
if (edgeControl.AlignLabelsToEdges)
{
if (p1.X > p2.X)
tmpAngle = GetAngleBetweenPoints(p2, p1);
tmpAngle = MathHelper.GetAngleBetweenPoints(p2, p1);
Angle = -tmpAngle * 180 / Math.PI;
}
p.Offset(edgeLength * Math.Cos(angleBetweenPoints), -edgeLength * Math.Sin(angleBetweenPoints));
if(edgeControl.AlignLabelsToEdges)
p = RotatePoint(new Point(p.X, p.Y - edgeControl.LabelVerticalOffset), p, Angle);
p = MathHelper.RotatePoint(new Point(p.X, p.Y - edgeControl.LabelVerticalOffset), p, Angle);
//optimized offset here
/*float x = 12.5f, y = 12.5f;
double sin = Math.Sin(angleBetweenPoints);
double cos = Math.Cos(angleBetweenPoints);
double sign = sin * cos / Math.Abs(sin * cos);
p.Offset(x * sin * sign + edgeLength * cos, y * cos * sign - edgeLength * sin);*/
Arrange(new Rect(p, desiredSize));
LastKnownRectSize = new Rect(p, desiredSize);
Arrange(LastKnownRectSize);
}
private static Point RotatePoint(Point pointToRotate, Point centerPoint, double angleInDegrees)
{
var angleInRadians = angleInDegrees * (Math.PI / 180);
var cosTheta = Math.Cos(angleInRadians);
var sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
(cosTheta * (pointToRotate.X - centerPoint.X) -
sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
Y =
(int)
(sinTheta * (pointToRotate.X - centerPoint.X) +
cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
};
}
internal Rect LastKnownRectSize;
}
}

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

@ -758,11 +758,20 @@ namespace GraphX
item.ShowArrows = _svShowEdgeArrows;
item.ShowLabel = _svShowEdgeLabels;
item.AlignLabelsToEdges = _svAlignEdgeLabels;
item.UpdateLabelPosition = _svUpdateLabelPosition;
HighlightBehaviour.SetIsHighlightEnabled(item, _svEdgeHlEnabled);
HighlightBehaviour.SetHighlightControl(item, _svEdgeHlObjectType);
HighlightBehaviour.SetHighlightEdges(item, EdgesType.All);
}
private bool _svUpdateLabelPosition;
public void UpdateEdgeLabelPosition(bool value)
{
_svUpdateLabelPosition = value;
foreach (var item in EdgesList)
item.Value.UpdateLabelPosition = value;
}
private EdgeDashStyle _svEdgeDashStyle = EdgeDashStyle.Solid;
/// <summary>
/// Sets all edges dash style
@ -862,6 +871,7 @@ namespace GraphX
_svVertexHlEdgesType = hlEdgesOfType;
foreach (var item in VertexList)
{
HighlightBehaviour.SetHighlighted(item.Value, false);
HighlightBehaviour.SetIsHighlightEnabled(item.Value, isEnabled);
HighlightBehaviour.SetHighlightControl(item.Value, hlObjectsOfType);
HighlightBehaviour.SetHighlightEdges(item.Value, hlEdgesOfType);
@ -882,6 +892,7 @@ namespace GraphX
foreach (var item in VertexList)
{
HighlightBehaviour.SetHighlighted(item.Value, false);
HighlightBehaviour.SetIsHighlightEnabled(item.Value, isEnabled);
HighlightBehaviour.SetHighlightControl(item.Value, hlObjectsOfType);
HighlightBehaviour.SetHighlightEdges(item.Value, EdgesType.All);
@ -938,6 +949,8 @@ namespace GraphX
var edgectrl = new EdgeControl(_vertexlist[item.Source], _vertexlist[item.Target], item) { Visibility = defaultVisibility };
InternalInsertEdge(item, edgectrl);
//setup path
if (_svShowEdgeLabels)
edgectrl.ShowLabel = true;
edgectrl.PrepareEdgePath();
//edgectrl.InvalidateChildren();
}
@ -945,6 +958,64 @@ namespace GraphX
if (LogicCore.EnableParallelEdges)
ParallelizeEdges();
if (_svShowEdgeLabels && LogicCore.EnableEdgeLabelsOverlapRemoval)
RemoveEdgeLabelsOverlap();
}
private void RemoveEdgeLabelsOverlap()
{
var sizes = new Dictionary<LabelOverlapData, Rect>();
var sz = GetVertexSizeRectangles();
foreach (var item in sz)
sizes.Add(new LabelOverlapData() { Id = item.Key.ID, IsVertex = true }, item.Value);
foreach (var item in EdgesList)
{
item.Value.UpdateLabelLayout();
sizes.Add(new LabelOverlapData() { Id = item.Key.ID, IsVertex = false }, item.Value.GetLabelSize());
}
var orAlgo = LogicCore.AlgorithmFactory.CreateFSAA(sizes, 15f, 15f);
orAlgo.Compute();
foreach (var item in orAlgo.Rectangles)
{
if (item.Key.IsVertex)
{
var vertex = VertexList.FirstOrDefault(a => a.Key.ID == item.Key.Id).Value;
if (vertex == null) throw new GX_InvalidDataException("RemoveEdgeLabelsOverlap() -> Vertex not found!");
vertex.SetPosition(new Point(item.Value.X + item.Value.Width * .5,item.Value.Y + item.Value.Height * .5));
//vertex.Arrange(item.Value);
}
else
{
var edge = EdgesList.FirstOrDefault(a => a.Key.ID == item.Key.Id).Value;
if (edge == null) throw new GX_InvalidDataException("RemoveEdgeLabelsOverlap() -> Edge not found!");
edge.SetCustomLabelSize(item.Value);
}
}
//recalculate route path for new vertex positions
if (LogicCore.AlgorithmStorage.EdgeRouting != null)
{
LogicCore.AlgorithmStorage.EdgeRouting.VertexSizes = GetVertexSizeRectangles();
LogicCore.AlgorithmStorage.EdgeRouting.VertexPositions = GetVertexPositions();
LogicCore.AlgorithmStorage.EdgeRouting.Compute();
if (LogicCore.AlgorithmStorage.EdgeRouting.EdgeRoutes != null)
foreach (var item in LogicCore.AlgorithmStorage.EdgeRouting.EdgeRoutes)
item.Key.RoutingPoints = item.Value;
}
foreach (var item in EdgesList)
{
item.Value.PrepareEdgePath(false, null, false);
}
//update edges
// UpdateAllEdges();
}
private class LabelOverlapData
{
public bool IsVertex;
public int Id;
}
/// <summary>

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

@ -97,7 +97,7 @@ namespace GraphX
internal virtual void OnVertexDoubleClick(VertexControl vc)
{
if (VertexDoubleClick != null)
VertexDoubleClick(this, new VertexSelectedEventArgs(vc, null));
VertexDoubleClick(this, new VertexSelectedEventArgs(vc, null, Keyboard.Modifiers));
}
/// <summary>
@ -105,10 +105,10 @@ namespace GraphX
/// </summary>
public virtual event VertexSelectedEventHandler VertexSelected;
internal virtual void OnVertexSelected(VertexControl vc, MouseButtonEventArgs e)
internal virtual void OnVertexSelected(VertexControl vc, MouseButtonEventArgs e, ModifierKeys keys)
{
if (VertexSelected != null)
VertexSelected(this, new VertexSelectedEventArgs(vc, e));
VertexSelected(this, new VertexSelectedEventArgs(vc, e, keys));
}
/// <summary>
/// Fires when mouse is over the vertex control
@ -118,7 +118,7 @@ namespace GraphX
internal virtual void OnVertexMouseEnter(VertexControl vc)
{
if (VertexMouseEnter != null)
VertexMouseEnter(this, new VertexSelectedEventArgs(vc, null));
VertexMouseEnter(this, new VertexSelectedEventArgs(vc, null, Keyboard.Modifiers));
if (MouseOverAnimation != null)
MouseOverAnimation.AnimateVertexForward(vc);
}
@ -142,7 +142,7 @@ namespace GraphX
internal virtual void OnVertexMouseLeave(VertexControl vc)
{
if (VertexMouseLeave != null)
VertexMouseLeave(this, new VertexSelectedEventArgs(vc, null));
VertexMouseLeave(this, new VertexSelectedEventArgs(vc, null, Keyboard.Modifiers));
if (MouseOverAnimation != null)
MouseOverAnimation.AnimateVertexBackward(vc);
}

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

@ -3,6 +3,7 @@ using System.Windows.Controls;
using System;
using System.Linq;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
using GraphX.Models;
@ -12,6 +13,7 @@ namespace GraphX
/// Visual vertex control
/// </summary>
[Serializable]
[TemplatePart(Name = "PART_vertexLabel", Type = typeof(VertexLabelControl))]
public class VertexControl: Control, IGraphControl
{
#region Properties
@ -57,6 +59,18 @@ namespace GraphX
public static readonly DependencyProperty RootCanvasProperty =
DependencyProperty.Register("RootArea", typeof(GraphAreaBase), typeof(VertexControl), new UIPropertyMetadata(null));
private bool _showLabel;
public bool ShowLabel
{
get { return _showLabel; }
set
{
_showLabel = value;
if (_vertexLabelControl != null)
_vertexLabelControl.Visibility = _showLabel ? Visibility.Visible : Visibility.Collapsed;
}
}
static VertexControl()
{
//override the StyleKey Property
@ -99,6 +113,8 @@ namespace GraphX
private void source_PositionChanged(object sender, EventArgs e)
{
if(ShowLabel && _vertexLabelControl != null)
_vertexLabelControl.UpdatePosition();
OnPositionChanged(new Point(0,0), GetPosition());
}
#endregion
@ -144,6 +160,21 @@ namespace GraphX
UpdateEventhandling(item);
}
private VertexLabelControl _vertexLabelControl;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (Template != null)
{
_vertexLabelControl = Template.FindName("PART_vertexLabel", this) as VertexLabelControl;
if(_vertexLabelControl != null)
_vertexLabelControl.UpdatePosition();
}
}
#region Events handling
internal void UpdateEventhandling(EventType typ)
@ -206,7 +237,7 @@ namespace GraphX
void VertexControl_Down(object sender, MouseButtonEventArgs e)
{
if (RootArea != null && Visibility == Visibility.Visible)
RootArea.OnVertexSelected(this, e);
RootArea.OnVertexSelected(this, e, Keyboard.Modifiers);
e.Handled = true;
}
#endregion
@ -226,5 +257,7 @@ namespace GraphX
EventOptions.Clean();
}
}
}
}

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

@ -0,0 +1,168 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace GraphX
{
public class VertexLabelControl : ContentControl
{
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle",
typeof(double),
typeof(VertexLabelControl),
new UIPropertyMetadata(0.0));
/// <summary>
/// Gets or sets label drawing angle in degrees
/// </summary>
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public static readonly DependencyProperty LabelPositionProperty = DependencyProperty.Register("LabelPosition",
typeof(Point),
typeof(VertexLabelControl),
new UIPropertyMetadata(new Point()));
/// <summary>
/// Gets or sets label position if LabelPositionMode is set to Coordinates
/// Position is always measured from top left VERTEX corner.
/// </summary>
public Point LabelPosition
{
get { return (Point)GetValue(LabelPositionProperty); }
set { SetValue(LabelPositionProperty, value); }
}
public static readonly DependencyProperty LabelPositionModeProperty = DependencyProperty.Register("LabelPositionMode",
typeof(VertexLabelPositionMode),
typeof(VertexLabelControl),
new UIPropertyMetadata(VertexLabelPositionMode.Sides));
/// <summary>
/// Gets or set label positioning mode
/// </summary>
public VertexLabelPositionMode LabelPositionMode
{
get { return (VertexLabelPositionMode)GetValue(LabelPositionModeProperty); }
set { SetValue(LabelPositionModeProperty, value); }
}
public static readonly DependencyProperty LabelPositionSideProperty = DependencyProperty.Register("LabelPositionSide",
typeof(VertexLabelPositionSide),
typeof(VertexLabelControl),
new UIPropertyMetadata(VertexLabelPositionSide.BottomRight));
/// <summary>
/// Gets or sets label position side if LabelPositionMode is set to Sides
/// </summary>
public VertexLabelPositionSide LabelPositionSide
{
get { return (VertexLabelPositionSide)GetValue(LabelPositionSideProperty); }
set { SetValue(LabelPositionSideProperty, value); }
}
public VertexLabelControl()
{
if (DesignerProperties.GetIsInDesignMode(this)) return;
LayoutUpdated += VertexLabelControl_LayoutUpdated;
HorizontalAlignment = HorizontalAlignment.Left;
VerticalAlignment = VerticalAlignment.Top;
}
void VertexLabelControl_LayoutUpdated(object sender, EventArgs e)
{
var vc = GetVertexControl(VisualParent);
if(vc == null || !vc.ShowLabel) return;
UpdatePosition();
}
private static VertexControl GetVertexControl(DependencyObject parent)
{
while (parent != null)
{
var control = parent as VertexControl;
if (control != null) return control;
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
internal void UpdatePosition()
{
if (double.IsNaN(DesiredSize.Width) || DesiredSize.Width == 0) return;
var vc = GetVertexControl(VisualParent);
if (vc == null) return;
if (LabelPositionMode == VertexLabelPositionMode.Sides)
{
Point pt;
switch (LabelPositionSide)
{
case VertexLabelPositionSide.TopRight:
pt = new Point(vc.DesiredSize.Width, -DesiredSize.Height);
break;
case VertexLabelPositionSide.BottomRight:
pt = new Point(vc.DesiredSize.Width, vc.DesiredSize.Height);
break;
case VertexLabelPositionSide.TopLeft:
pt = new Point(-DesiredSize.Width, -DesiredSize.Height);
break;
case VertexLabelPositionSide.BottomLeft:
pt = new Point(-DesiredSize.Width, vc.DesiredSize.Height);
break;
case VertexLabelPositionSide.Top:
pt = new Point(vc.DesiredSize.Width *.5 - DesiredSize.Width *.5, -DesiredSize.Height);
break;
case VertexLabelPositionSide.Bottom:
pt = new Point(vc.DesiredSize.Width * .5 - DesiredSize.Width * .5, vc.DesiredSize.Height);
break;
case VertexLabelPositionSide.Left:
pt = new Point(-DesiredSize.Width, vc.DesiredSize.Height * .5f - DesiredSize.Height * .5);
break;
case VertexLabelPositionSide.Right:
pt = new Point(vc.DesiredSize.Width, vc.DesiredSize.Height * .5f - DesiredSize.Height * .5);
break;
default:
throw new GX_InvalidDataException("UpdatePosition() -> Unknown vertex label side!");
}
LastKnownRectSize = new Rect(pt, DesiredSize);
} else
{
LastKnownRectSize = new Rect(LabelPosition, DesiredSize);
}
Arrange(LastKnownRectSize);
}
internal Rect LastKnownRectSize;
}
public enum VertexLabelPositionMode
{
/// <summary>
/// Vertex label is positioned on one of the sides
/// </summary>
Sides,
/// <summary>
/// Vertex label is positioned using custom coordinates
/// </summary>
Coordinates
}
public enum VertexLabelPositionSide
{
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Top, Right, Bottom, Left
}
}

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

@ -193,6 +193,7 @@ namespace GraphX.Controls
{
// create VisualBrush for the view finder display panel
CreateVisualBrushForViewFinder(Content as Visual);
_viewFinderDisplay.Background = this.Background;
// hook up event handlers for dragging and resizing the viewport
_viewFinderDisplay.MouseMove += ViewFinderDisplayMouseMove;

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

@ -86,6 +86,7 @@
<Compile Include="Animations\MoveSimpleAnimation.cs" />
<Compile Include="Behaviours\DragBehaviour.cs" />
<Compile Include="Behaviours\HighlightBehaviour.cs" />
<Compile Include="Controls\VertexLabelControl.cs" />
<Compile Include="Controls\ZoomControl\Converters\RoundedValueConverter.cs" />
<Compile Include="Controls\ZoomControl\Converters\VisibilityToBoolConverter.cs" />
<Compile Include="Controls\ZoomControl\SupportClasses\AreaSelectedEventArgs.cs" />

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

@ -7,12 +7,14 @@ namespace GraphX.Models
{
public VertexControl VertexControl { get; private set; }
public MouseButtonEventArgs MouseArgs { get; private set; }
public ModifierKeys Modifiers { get; private set; }
public VertexSelectedEventArgs(VertexControl vc, MouseButtonEventArgs e)
public VertexSelectedEventArgs(VertexControl vc, MouseButtonEventArgs e, ModifierKeys keys)
: base()
{
VertexControl = vc;
MouseArgs = e;
Modifiers = keys;
}
}
}

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

@ -1,5 +1,6 @@
using System;
using System.Windows;
using System.Windows.Input;
namespace GraphX.Models
{

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

@ -1,4 +1,5 @@
using GraphX.GraphSharp;
using System.Reflection;
using GraphX.GraphSharp;
using GraphX.GraphSharp.Algorithms.EdgeRouting;
using GraphX.GraphSharp.Algorithms.Layout;
using GraphX.GraphSharp.Algorithms.Layout.Simple.Circular;
@ -134,6 +135,11 @@ namespace GraphX.Logic.Models
}
}
public IOverlapRemovalAlgorithm<T> CreateFSAA<T>(IDictionary<T, Rect> rectangles, float horGap, float vertGap) where T : class
{
return new FSAAlgorithm<T>(rectangles, new OverlapRemovalParameters() { HorizontalGap = horGap, VerticalGap = vertGap});
}
public IOverlapRemovalParameters CreateOverlapRemovalParameters(OverlapRemovalAlgorithmTypeEnum algorithmType)
{
switch (algorithmType)

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

@ -34,6 +34,7 @@ namespace GraphX.Logic
public IAlgorithmStorage<TVertex, TEdge> AlgorithmStorage { get; set; }
#endregion
public bool EnableEdgeLabelsOverlapRemoval { get; set; }
public IExternalLayout<TVertex> ExternalLayoutAlgorithm { get; set; }
public IExternalOverlapRemoval<TVertex> ExternalOverlapRemovalAlgorithm { get; set; }