-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathEdge.cs
More file actions
186 lines (163 loc) · 6.1 KB
/
Edge.cs
File metadata and controls
186 lines (163 loc) · 6.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
using System;
using System.Collections.Generic;
using System.Linq;
using static Rubjerg.Graphviz.FFI.GraphvizFFI;
namespace Rubjerg.Graphviz;
public class Edge : CGraphThing
{
/// <summary>
/// rootgraph must not be null
/// </summary>
internal Edge(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
internal static Edge? Get(Graph graph, Node tail, Node head, string? name)
{
name = NameString(name);
IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 0);
if (ptr == IntPtr.Zero)
return null;
return new Edge(ptr, graph.MyRootGraph);
}
internal static Edge GetOrCreate(Graph graph, Node tail, Node head, string? name)
{
name = NameString(name);
IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 1);
return new Edge(ptr, graph.MyRootGraph);
}
/// <summary>
/// Introduces an attribute for edges in the given graph by given a default.
/// A given default can be overwritten by calling this method again.
/// </summary>
public static void IntroduceAttribute(RootGraph root, string name, string deflt)
{
_ = deflt ?? throw new ArgumentNullException(nameof(deflt));
Agattr(root._ptr, 2, name, deflt);
}
public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
{
_ = deflt ?? throw new ArgumentNullException(nameof(deflt));
AgattrHtml(root._ptr, 2, name, deflt);
}
protected internal IntPtr HeadPtr()
{
return Aghead(_ptr);
}
protected internal IntPtr TailPtr()
{
return Agtail(_ptr);
}
public Node Head()
{
return new Node(HeadPtr(), MyRootGraph);
}
public Node Tail()
{
return new Node(TailPtr(), MyRootGraph);
}
public Node OppositeEndpoint(Node node)
{
var tail = Tail();
var head = Head();
return node == tail ? head : tail;
}
public bool IsAdjacentTo(Node node)
{
return node.Equals(Head()) || node.Equals(Tail());
}
public bool IsBetween(Node node1, Node node2)
{
return IsAdjacentTo(node1) && IsAdjacentTo(node2);
}
/// <summary>
/// An edge can define a cluster as logical tail.
/// This is used to fake edges to and from clusters by clipping the edge on the borders of the logical tail.
/// </summary>
/// <returns></returns>
public void SetLogicalTail(SubGraph ltail)
{
if (!ltail.IsCluster())
throw new InvalidOperationException("ltail must be a cluster");
if (!MyRootGraph.IsCompound())
throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used");
string? ltailname = ltail.GetName();
SetAttribute("ltail", ltailname);
}
/// <summary>
/// An edge can define a cluster as logical head.
/// This is used to fake edges to and from clusters by clipping the edge on the borders of the logical head.
/// </summary>
public void SetLogicalHead(SubGraph lhead)
{
if (!lhead.IsCluster())
throw new InvalidOperationException("ltail must be a cluster");
if (!MyRootGraph.IsCompound())
throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used");
string? lheadname = lhead.GetName();
SetAttribute("lhead", lheadname);
}
/// <summary>
/// Port names cannot contain certain characters, and other characters must be escaped.
/// This function converts a string to an ID that is valid as a port name.
/// It makes sure there are no collisions.
/// </summary>
public static string ConvertUidToPortName(string id)
{
string result = id;
foreach (char c in new[] { '<', '>', '{', '}', '|', ':' })
{
result = result.Replace("+", "[+]");
result = result.Replace(c, '+');
}
return result;
}
// Because there are two valid pointers to each edge, we have to override the default equals behaviour
// which simply compares the wrapped pointers.
public override bool Equals(GraphvizThing? obj)
{
if (obj is Edge)
return Ageqedge(_ptr, obj._ptr);
return false;
}
public override int GetHashCode()
{
// Return the ptr to the in-edge, which is unique and consistent for each edge.
// The following line can result in an OverflowException:
//return (int) agmkin(ptr);
return (int)(long)Agmkin(_ptr);
}
#region layout attributes
/// <summary>
/// This method only returns the first spline that is defined.
/// Returns null if no splines exist.
/// </summary>
public PointD[] GetFirstSpline()
{
return GetSplines().FirstOrDefault();
}
/// <summary>
/// The splines contain 3n+1 points, just like expected by .net drawing methods.
/// Sometimes there are multiple splines per edge. However, this is not always correct:
/// https://github.com/ellson/graphviz/issues/1277
/// Edge arrows are ignored.
/// </summary>
public IEnumerable<PointD[]> GetSplines()
{
return GetDrawing().OfType<XDotOp.UnfilledBezier>().Select(x => x.Points);
}
/// <summary>
/// See documentation on <see cref="XDotOp"/>
/// </summary>
public IReadOnlyList<XDotOp> GetHeadArrowDrawing() => GetXDotValue(this, "_hdraw_");
/// <summary>
/// See documentation on <see cref="XDotOp"/>
/// </summary>
public IReadOnlyList<XDotOp> GetTailArrowDrawing() => GetXDotValue(this, "_tdraw_");
/// <summary>
/// See documentation on <see cref="XDotOp"/>
/// </summary>
public IReadOnlyList<XDotOp> GetHeadLabelDrawing() => GetXDotValue(this, "_hldraw_");
/// <summary>
/// See documentation on <see cref="XDotOp"/>
/// </summary>
public IReadOnlyList<XDotOp> GetTailLabelDrawing() => GetXDotValue(this, "_tldraw_");
#endregion
}