org.jungrapht.visualization.layout.algorithms.eiglsperger.ExtendedEiglspergerRunnable Maven / Gradle / Ivy
The newest version!
package org.jungrapht.visualization.layout.algorithms.eiglsperger;
import static org.jungrapht.visualization.layout.util.PropertyLoader.PREFIX;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.LongSummaryStatistics;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.jgrapht.Graph;
import org.jgrapht.alg.util.NeighborCache;
import org.jungrapht.visualization.layout.algorithms.Layered;
import org.jungrapht.visualization.layout.algorithms.sugiyama.*;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.layout.model.Rectangle;
import org.jungrapht.visualization.layout.util.PropertyLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @see "Methods for Visual Understanding Hierarchical System Structures. KOZO SUGIYAMA, MEMBER,
* IEEE, SHOJIRO TAGAWA, AND MITSUHIKO TODA, MEMBER, IEEE"
* @see "An E log E Line Crossing Algorithm for Levelled Graphs. Vance Waddle and Ashok Malhotra IBM
* Thomas J. Watson Research Center"
* @see "Simple and Efficient Bilayer Cross Counting. Wilhelm Barth, Petra Mutzel, Institut für
* Computergraphik und Algorithmen Technische Universität Wien, Michael Jünger, Institut für
* Informatik Universität zu Köln"
* @see "Fast and Simple Horizontal Coordinate Assignment, Ulrik Brandes and Boris Köpf, Department
* of Computer & Information Science, University of Konstanz"
* @see "An Efficient Implementation of Sugiyama's Algorithm for Layered Graph Drawing. Markus
* Eiglsperger, Martin Siebenhaller, Michael Kaufman"
* @param vertex type
* @param edge type
*/
public class ExtendedEiglspergerRunnable extends EiglspergerRunnable
implements Runnable {
private static final Logger log = LoggerFactory.getLogger(ExtendedEiglspergerRunnable.class);
static {
PropertyLoader.load();
}
/**
* a Builder to create a configured instance
*
* @param the vertex type
* @param the edge type
* @param the type that is built
* @param the builder type
*/
public static class Builder<
V, E, T extends ExtendedEiglspergerRunnable, B extends Builder>
extends EiglspergerRunnable.Builder {
boolean doUpLeft;
boolean doDownLeft;
boolean doUpRight;
boolean doDownRight;
public B doUpLeft(boolean doUpLeft) {
this.doUpLeft = doUpLeft;
return self();
}
public B doUpRight(boolean doUpRight) {
this.doUpRight = doUpRight;
return self();
}
public B doDownLeft(boolean doDownLeft) {
this.doDownLeft = doDownLeft;
return self();
}
public B doDownRight(boolean doDownRight) {
this.doDownRight = doDownRight;
return self();
}
/** {@inheritDoc} */
public T build() {
return (T) new ExtendedEiglspergerRunnable<>(this);
}
}
/**
* @param vertex type
* @param edge type
* @return a Builder ready to configure
*/
public static Builder builder() {
return new Builder<>();
}
boolean doUpLeft;
boolean doDownLeft;
boolean doUpRight;
boolean doDownRight;
protected ExtendedEiglspergerRunnable(Builder builder) {
super(builder);
this.doUpLeft = builder.doUpLeft;
this.doDownLeft = builder.doDownLeft;
this.doUpRight = builder.doUpRight;
this.doDownRight = builder.doDownRight;
}
@Override
public void run() {
this.graph = layoutModel.getGraph();
if (graph.vertexSet().isEmpty()) {
return;
}
if (graph.vertexSet().size() == 1) {
V v = graph.vertexSet().stream().findFirst().get();
layoutModel.setSize(50, layoutModel.getHeight());
layoutModel.set(v, layoutModel.getWidth() / 2, layoutModel.getHeight() / 2);
return;
}
long startTime = System.currentTimeMillis();
TransformedGraphSupplier transformedGraphSupplier = new TransformedGraphSupplier<>(graph);
this.svGraph = transformedGraphSupplier.get();
neighborCache = new NeighborCache<>(svGraph);
long transformTime = System.currentTimeMillis();
log.trace("transform Graph took {}", (transformTime - startTime));
Collection feedbacks;
if (edgeComparator == Layered.noopComparator) {
GreedyFeedbackArcFunction greedyFeedbackArcFunction = new GreedyFeedbackArcFunction<>();
feedbacks = greedyFeedbackArcFunction.apply(graph);
} else {
ConstructiveFeedbackArcFunction constructiveFeedbackArcFunction =
new ConstructiveFeedbackArcFunction<>(edgeComparator);
feedbacks = constructiveFeedbackArcFunction.apply(graph);
}
Collection> feedbackArcs =
svGraph
.edgeSet()
.stream()
.filter(e -> feedbacks.contains(e.getEdge()))
.collect(Collectors.toSet());
// reverse the direction of feedback arcs so that they no longer introduce cycles in the graph
// the feedback arcs will be processed later to draw with the correct direction and correct articulation points
for (LE se : feedbackArcs) {
svGraph.removeEdge(se);
LE newEdge = LE.of(se.getEdge(), se.getTarget(), se.getSource());
svGraph.addEdge(newEdge.getSource(), newEdge.getTarget(), newEdge);
}
long cycles = System.currentTimeMillis();
log.trace("remove cycles took {}", (cycles - transformTime));
if (cancelled || Thread.currentThread().isInterrupted()) {
log.trace("interrupted before layering, cancelled: {}", cancelled);
return;
}
Comparator> svComparator =
(e1, e2) -> edgeComparator.compare(e1.getEdge(), e2.getEdge());
List>> layers;
switch (layering) {
case LONGEST_PATH:
if (edgeComparator != Layered.noopComparator) {
layers = GraphLayers.longestPath(svGraph, svComparator);
} else {
layers = GraphLayers.longestPath(svGraph);
}
break;
case COFFMAN_GRAHAM:
if (edgeComparator != Layered.noopComparator) {
layers = GraphLayers.coffmanGraham(svGraph, neighborCache, 0, svComparator);
} else {
layers = GraphLayers.coffmanGraham(svGraph, neighborCache, 0);
}
break;
case NETWORK_SIMPLEX:
if (edgeComparator != Layered.noopComparator) {
layers = GraphLayers.networkSimplex(svGraph, svComparator);
} else {
layers = GraphLayers.networkSimplex(svGraph);
}
break;
case TOP_DOWN:
default:
if (edgeComparator != Layered.noopComparator) {
layers = GraphLayers.assign(svGraph, svComparator);
} else {
layers = GraphLayers.assign(svGraph);
}
}
if (minimizeEdgeLength) {
GraphLayers.minimizeEdgeLength(svGraph, layers);
}
long assignLayersTime = System.currentTimeMillis();
log.trace("assign layers took {} ", (assignLayersTime - cycles));
if (log.isTraceEnabled()) {
GraphLayers.checkLayers(layers);
}
Synthetics synthetics = new Synthetics<>(svGraph);
List> edges = new ArrayList<>(svGraph.edgeSet());
LV[][] layersArray = synthetics.createVirtualVerticesAndEdges(edges, layers);
if (log.isTraceEnabled()) {
GraphLayers.checkLayers(layersArray);
}
long syntheticsTime = System.currentTimeMillis();
log.trace("synthetics took {}", (syntheticsTime - assignLayersTime));
if (svGraph.edgeSet().size() > 200) {
maxLevelCross = 2;
}
stepsForward = new EiglspergerStepsForward<>(svGraph, neighborCache, layersArray, transpose);
stepsBackward = new EiglspergerStepsBackward<>(svGraph, neighborCache, layersArray, transpose);
int bestCrossCount = Integer.MAX_VALUE;
Graph, Integer> bestCompactionGraph = null;
for (int i = 0; i < maxLevelCross; i++) {
if (cancelled || Thread.currentThread().isInterrupted()) {
log.trace("interrupted in level cross, cancelled: {}", cancelled);
return;
}
if (i % 2 == 0) {
int sweepCrossCount = stepsForward.sweep(layersArray);
Graph, Integer> compactionGraph = stepsForward.compactionGraph;
if (sweepCrossCount < bestCrossCount) {
bestCrossCount = sweepCrossCount;
vertexMetadataMap = save(layersArray);
bestCompactionGraph = copy(compactionGraph);
} else {
if (log.isTraceEnabled()) {
log.trace("best:{}", layersArray);
}
// break;
}
} else {
int sweepCrossCount = stepsBackward.sweep(layersArray);
Graph, Integer> compactionGraph = stepsBackward.compactionGraph;
if (sweepCrossCount < bestCrossCount) {
bestCrossCount = sweepCrossCount;
vertexMetadataMap = save(layersArray);
bestCompactionGraph = copy(compactionGraph);
} else {
if (log.isTraceEnabled()) {
log.trace("best:{}", layersArray);
}
// break;
}
}
}
log.trace("bestCrossCount: {}", bestCrossCount);
restore(layersArray, vertexMetadataMap);
Arrays.stream(layersArray)
.forEach(layer -> Arrays.sort(layer, Comparator.comparingInt(LV::getIndex)));
// figure out the avg size of rendered vertex
Rectangle avgVertexBounds = maxVertexBounds(layersArray, vertexShapeFunction);
int horizontalOffset =
(int)
Math.max(
avgVertexBounds.width,
Integer.getInteger(PREFIX + "mincross.horizontalOffset", 50));
int verticalOffset =
(int)
Math.max(
avgVertexBounds.height, Integer.getInteger(PREFIX + "mincross.verticalOffset", 50));
GraphLayers.checkLayers(layersArray);
Map, Point> vertexPointMap = new HashMap<>();
// update the indices of the all layers
for (LV[] value : layersArray) {
for (int j = 0; j < value.length; j++) {
value[j].setIndex(j);
}
}
if (cancelled || Thread.currentThread().isInterrupted()) {
log.trace("interrupted before compaction, cancelled: {}", cancelled);
return;
}
if (straightenEdges) {
SelectiveEiglspergerHorizontalCoordinateAssignment horizontalCoordinateAssignment =
new SelectiveEiglspergerHorizontalCoordinateAssignment(
layersArray,
svGraph,
bestCompactionGraph,
new HashSet<>(),
horizontalOffset,
verticalOffset,
doUpLeft,
doUpRight,
doDownLeft,
doDownRight);
horizontalCoordinateAssignment.horizontalCoordinateAssignment();
GraphLayers.checkLayers(layersArray);
for (LV[] lvs : layersArray) {
for (LV EiglspergerVertex : lvs) {
vertexPointMap.put(EiglspergerVertex, EiglspergerVertex.getPoint());
}
}
} else {
Unaligned.centerPoints(layersArray, vertexShapeFunction, horizontalOffset, verticalOffset);
}
Map rowWidthMap = new HashMap<>(); // all the row widths
Map rowMaxHeightMap = new HashMap<>(); // all the row heights
int layerIndex = 0;
int totalHeight = 0;
int totalWidth = 0;
for (LV[] lvs : layersArray) {
int width = horizontalOffset;
int maxHeight = 0;
for (LV v : lvs) {
if (!(v instanceof SyntheticLV)) {
Rectangle bounds = vertexShapeFunction.apply(v.getVertex());
width += bounds.width + horizontalOffset;
maxHeight = Math.max(maxHeight, (int) bounds.height);
} else {
width += horizontalOffset;
}
}
rowWidthMap.put(layerIndex, width);
rowMaxHeightMap.put(layerIndex, maxHeight);
layerIndex++;
}
int widestRowWidth = rowWidthMap.values().stream().mapToInt(v -> v).max().orElse(0);
int x = horizontalOffset;
int y = verticalOffset;
layerIndex = 0;
if (log.isTraceEnabled()) {
log.trace("layerMaxHeights {}", rowMaxHeightMap);
}
for (LV[] lvs : layersArray) {
int previousVertexWidth = 0;
// offset against widest row
x += (widestRowWidth - rowWidthMap.get(layerIndex)) / 2;
y += rowMaxHeightMap.get(layerIndex) / 2;
if (layerIndex > 0) {
y += rowMaxHeightMap.get(layerIndex - 1) / 2;
}
int rowWidth = 0;
for (LV EiglspergerVertex : lvs) {
int vertexWidth = 0;
if (!(EiglspergerVertex instanceof SyntheticLV)) {
vertexWidth = (int) vertexShapeFunction.apply(EiglspergerVertex.getVertex()).width;
}
x += previousVertexWidth / 2 + vertexWidth / 2 + horizontalOffset;
rowWidth = x + vertexWidth / 2;
log.trace("layerIndex {} y is {}", layerIndex, y);
previousVertexWidth = vertexWidth;
}
totalWidth = Math.max(totalWidth, rowWidth);
x = horizontalOffset;
y += verticalOffset;
totalHeight = y + rowMaxHeightMap.get(layerIndex) / 2;
layerIndex++;
}
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = -1;
int maxY = -1;
for (Point p : vertexPointMap.values()) {
minX = Math.min((int) p.x, minX);
maxX = Math.max((int) p.x, maxX);
minY = Math.min((int) p.y, minY);
maxY = Math.max((int) p.y, maxY);
}
maxX += horizontalOffset;
maxY += verticalOffset;
int pointRangeWidth = maxX - minX;
int pointRangeHeight = maxY - minY;
int offsetX = 0;
int offsetY = 0;
if (minX < 0) {
offsetX += -minX + horizontalOffset;
}
if (minY < 0) {
offsetY += -minY + verticalOffset;
}
pointRangeWidth *= 1.1;
pointRangeHeight *= 1.1;
int maxDimension = Math.max(totalWidth, totalHeight);
layoutModel.setSize(
multiComponent ? totalWidth : Math.max(maxDimension, layoutModel.getWidth()),
Math.max(maxDimension, layoutModel.getHeight()));
long pointsSetTime = System.currentTimeMillis();
double scalex = (double) layoutModel.getWidth() / pointRangeWidth;
double scaley = (double) layoutModel.getHeight() / pointRangeHeight;
for (Map.Entry, Point> entry : vertexPointMap.entrySet()) {
Point p = entry.getValue();
Point q = Point.of((offsetX + p.x) * scalex, (offsetY + p.y) * scaley);
entry.setValue(q);
}
// now all the vertices in layers (best) have points associated with them
// every vertex in vertexMap has a point value
svGraph.vertexSet().forEach(v -> v.setPoint(vertexPointMap.get(v)));
if (postStraighten) {
synthetics.alignArticulatedEdges();
}
List> articulatedEdges = synthetics.makeArticulatedEdges();
Set feedbackEdges = new HashSet<>();
feedbackArcs.forEach(a -> feedbackEdges.add(a.getEdge()));
articulatedEdges
.stream()
.filter(ae -> feedbackEdges.contains(ae.getEdge()))
.forEach(
ae -> {
svGraph.removeEdge(ae);
LE reversed = ae.reversed();
svGraph.addEdge(reversed.getSource(), reversed.getTarget(), reversed);
});
for (ArticulatedEdge ae : articulatedEdges) {
List points = new ArrayList<>();
if (feedbackEdges.contains(ae.getEdge())) {
points.add(ae.getTarget().getPoint());
points.addAll(ae.reversed().getIntermediatePoints());
points.add(ae.getSource().getPoint());
} else {
points.add(ae.getSource().getPoint());
points.addAll(ae.getIntermediatePoints());
points.add(ae.getTarget().getPoint());
}
edgePointMap.put(ae.edge, points);
}
long articulatedEdgeTime = System.currentTimeMillis();
log.trace("articulated edges took {}", (articulatedEdgeTime - pointsSetTime));
if (cancelled) {
log.debug("interrupted before setting layoutModel from svGraph, cancelled: {}", cancelled);
return;
}
svGraph.vertexSet().forEach(v -> layoutModel.set(v.getVertex(), v.getPoint()));
}
private static Rectangle maxVertexBounds(
LV[][] layers, Function vertexShapeFunction) {
// figure out the largest rendered vertex
Rectangle maxVertexBounds = Rectangle.IDENTITY;
for (LV[] layer : layers) {
for (LV vlv : layer) {
if (!(vlv instanceof SyntheticLV)) {
Rectangle bounds = vertexShapeFunction.apply(vlv.getVertex());
int width = (int) Math.max(bounds.width, maxVertexBounds.width);
int height = (int) Math.max(bounds.height, maxVertexBounds.height);
maxVertexBounds = Rectangle.of(width, height);
}
}
}
return maxVertexBounds;
}
private static Rectangle avgVertexBounds(
LV[][] layers, Function vertexShapeFunction) {
LongSummaryStatistics w = new LongSummaryStatistics();
LongSummaryStatistics h = new LongSummaryStatistics();
for (LV[] layer : layers) {
for (LV vlv : layer) {
if (!(vlv instanceof SyntheticLV)) {
Rectangle bounds = vertexShapeFunction.apply(vlv.getVertex());
w.accept((int) bounds.width);
h.accept((int) bounds.height);
}
}
}
return Rectangle.of((int) w.getAverage(), (int) h.getAverage());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy