/
EdgeCrossingsAnalysis.java
292 lines (261 loc) · 10.7 KB
/
EdgeCrossingsAnalysis.java
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
* KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
*
* http://www.informatik.uni-kiel.de/rtsys/kieler/
*
* Copyright 2010 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
*
* This code is provided under the terms of the Eclipse Public License (EPL).
* See the file epl-v10.html for the license text.
*/
package de.cau.cs.kieler.grana.analyses;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.elk.core.math.ElkMath;
import org.eclipse.elk.core.math.KVector;
import org.eclipse.elk.core.math.KVectorChain;
import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.EdgeRouting;
import org.eclipse.elk.core.util.ElkUtil;
import org.eclipse.elk.core.util.IElkProgressMonitor;
import org.eclipse.elk.graph.ElkEdge;
import org.eclipse.elk.graph.ElkEdgeSection;
import org.eclipse.elk.graph.ElkNode;
import org.eclipse.elk.graph.ElkPort;
import org.eclipse.elk.graph.util.ElkGraphUtil;
import de.cau.cs.kieler.grana.AnalysisContext;
import de.cau.cs.kieler.grana.AnalysisOptions;
import de.cau.cs.kieler.grana.IAnalysis;
/**
* A graph analysis that computes the number of edge crossings. It assumes that
* the edge bend points describe polylines. Returns a five-component result
* {@code (int min, float avg, int max, int sum, float onefold)}. Where
* onefold is the ratio of edges that cross at least once and all edges.
*
* @author mri
* @author cds
* @author msp
* @author uru
* @kieler.design proposed by msp
* @kieler.rating proposed yellow 2012-07-10 msp
*/
public class EdgeCrossingsAnalysis implements IAnalysis {
/**
* Identifier of the edge crossings analysis.
*/
public static final String ID = "de.cau.cs.kieler.grana.edgeCrossings";
/** tolerance for double equality. */
private static final double TOLERANCE = 1e-4;
/** Holder class for results. */
public static class EdgeCrossingsResult {
// SUPPRESS CHECKSTYLE NEXT 10 Javadoc|VisibilityModifier
public int min = 0;
public int max = 0;
public int sum = 0;
public double average = 0;
public double onefold = 0;
public EdgeCrossingsResult() { }
public EdgeCrossingsResult(final int min, final int max, final int sum,
final double average, final double onefold) {
this.min = min;
this.max = max;
this.sum = sum;
this.average = average;
this.onefold = onefold;
}
}
/**
* Returns whether two line segments have an intersection.
*
* @param p1
* start point of the first line segment
* @param p2
* end point of the first line segment
* @param q1
* start point of the second line segment
* @param q2
* end point of the second line segment
* @return true if the lines have an intersection
*/
private static boolean hasIntersection(final KVector p1, final KVector p2,
final KVector q1, final KVector q2) {
double s = (q2.y - q1.y) * (p2.x - p1.x) - (q2.x - q1.x) * (p2.y - p1.y);
// are the line segments parallel?
if (Math.abs(s) < TOLERANCE) {
return false;
}
double a1 = (q2.x - q1.x) * (p1.y - q1.y) - (q2.y - q1.y) * (p1.x - q1.x);
double a2 = (p2.x - p1.x) * (p1.y - q1.y) - (p2.y - p1.y) * (p1.x - q1.x);
double t1 = a1 / s;
double t2 = a2 / s;
// the line segments intersect when t1 and t2 lie in the interval (0,1)
return 0 < t1 && t1 < 1 && 0 < t2 && t2 < 1;
}
/**
* Computes the number of crossings between two vector chains.
*
* @param chain1 the first vector chain
* @param chain2 the second vector chain
* @return the number of crossings
*/
private static int computeNumberOfCrossings(final KVectorChain chain1, final KVectorChain chain2) {
int numberOfCrossings = 0;
Iterator<KVector> points1 = chain1.iterator();
KVector p1 = points1.next();
while (points1.hasNext()) {
KVector p2 = points1.next();
numberOfCrossings += computeNumberOfCrossings(p1, p2, chain2);
p1 = p2;
}
return numberOfCrossings;
}
/**
* Computes the number of crossings of a line and a vector chain.
*
* @param p1 start point of the line
* @param p2 end point of the line
* @param chain2 a vector chain
* @return the number of crossings
*/
private static int computeNumberOfCrossings(final KVector p1, final KVector p2,
final KVectorChain chain2) {
int numberOfCrossings = 0;
Iterator<KVector> points2 = chain2.iterator();
KVector q1 = points2.next();
while (points2.hasNext()) {
KVector q2 = points2.next();
numberOfCrossings += hasIntersection(p1, p2, q1, q2) ? 1 : 0;
q1 = q2;
}
return numberOfCrossings;
}
/**
* Count crossings between the passed edges. The two passed lists must be aligned,
* i.e. {@code chains[i]} must contain the bendpoints of {@code edges[i]}.
*
* @param edges
* the edges
* @param chains
* a chain of bendpoints describing each edge's path in <em>absolute</em>
* coordinates.
* @return results
*/
public static EdgeCrossingsResult countCrossings(final List<ElkEdge> edges,
final List<KVectorChain> chains) {
if (edges.isEmpty()) {
return new EdgeCrossingsResult();
}
if (edges.size() != chains.size()) {
throw new IllegalArgumentException("'edges' and 'chains' lists must be aligned.");
}
// count the number of crossings between all edges of the compound graph
int edgeCount = edges.size();
int[] crossings = new int[edgeCount];
for (int i = 0; i < edgeCount; i++) {
ElkEdge edge1 = edges.get(i);
KVectorChain chain1 = chains.get(i);
ElkNode source1 = ElkGraphUtil.getSourceNode(edge1);
ElkNode target1 = ElkGraphUtil.getTargetNode(edge1);
ElkPort sourcePort1 = ElkGraphUtil.getSourcePort(edge1);
ElkPort targetPort1 = ElkGraphUtil.getTargetPort(edge1);
for (int j = i + 1; j < edgeCount; j++) {
ElkEdge edge2 = edges.get(j);
ElkNode source2 = ElkGraphUtil.getSourceNode(edge2);
ElkNode target2 = ElkGraphUtil.getTargetNode(edge2);
ElkPort sourcePort2 = ElkGraphUtil.getSourcePort(edge2);
ElkPort targetPort2 = ElkGraphUtil.getTargetPort(edge2);
boolean samePort = false;
samePort |= sourcePort1 != null
&& (sourcePort1.equals(sourcePort2) || sourcePort1.equals(targetPort2));
samePort |= targetPort1 != null
&& (targetPort1.equals(targetPort2) || targetPort1.equals(sourcePort2));
samePort |= source1.getProperty(CoreOptions.HYPERNODE)
&& (source1.equals(source2) || source1.equals(target2));
samePort |= target1.getProperty(CoreOptions.HYPERNODE)
&& (target1.equals(target2) || target1.equals(source2));
if (!samePort) {
KVectorChain chain2 = chains.get(j);
int c = computeNumberOfCrossings(chain1, chain2);
crossings[i] += c;
crossings[j] += c;
}
}
}
// determine minimum, maximum, sum, and average value
int min = Integer.MAX_VALUE;
int max = 0;
int sum = 0;
float avg = 0.0f;
for (int i = 0; i < edgeCount; i++) {
sum += crossings[i];
min = Math.min(min, crossings[i]);
max = Math.max(max, crossings[i]);
}
if (edgeCount > 0) {
avg = (float) sum / edgeCount;
} else {
min = 0;
}
sum /= 2;
// count edges that cross at least once
int onefold = 0;
for (int i = 0; i < edgeCount; i++) {
if (crossings[i] > 0) {
onefold++;
}
}
// normalize
float onefoldNorm = onefold / (float) edgeCount;
return new EdgeCrossingsResult(min, max, sum, avg, onefoldNorm);
}
/**
* {@inheritDoc}
*/
public Object doAnalysis(final ElkNode parentNode,
final AnalysisContext context,
final IElkProgressMonitor progressMonitor) {
progressMonitor.begin("Edge Crossings analysis", 1);
boolean hierarchy = parentNode.getProperty(AnalysisOptions.ANALYZE_HIERARCHY);
// collect all edges and translate their coordinates to absolute
LinkedList<ElkNode> nodeQueue = new LinkedList<>();
List<ElkEdge> edges = new ArrayList<>();
List<KVectorChain> chains = new ArrayList<>();
nodeQueue.addAll(parentNode.getChildren());
while (!nodeQueue.isEmpty()) {
// poll the first element
ElkNode node = nodeQueue.poll();
// collect the outgoing edges
for (ElkEdge edge : ElkGraphUtil.allOutgoingEdges(node)) {
if (!hierarchy && edge.isHierarchical()) {
continue;
}
ElkEdgeSection section = ElkGraphUtil.firstEdgeSection(edge, false, false);
KVectorChain chain = ElkUtil.createVectorChain(section);
// translate the bend point coordinates to absolute
ElkNode referenceNode = edge.getContainingNode();
KVector referencePoint = new KVector();
ElkUtil.toAbsolute(referencePoint, referenceNode);
chain.offset(referencePoint);
// transform spline control points to approximated bend points
if (edge.getProperty(CoreOptions.EDGE_ROUTING) == EdgeRouting.SPLINES) {
chain = ElkMath.approximateBezierSpline(chain);
}
edges.add(edge);
chains.add(chain);
}
// enqueue the child nodes
if (hierarchy) {
nodeQueue.addAll(node.getChildren());
}
}
EdgeCrossingsResult results = countCrossings(edges, chains);
progressMonitor.done();
return new Object[] { results.min, results.average, results.max, results.sum,
results.onefold };
}
}