org.jungrapht.visualization.layout.algorithms.sugiyama.SugiyamaRunnable Maven / Gradle / Ivy
The newest version!
package org.jungrapht.visualization.layout.algorithms.sugiyama;
import static org.jungrapht.visualization.layout.util.PropertyLoader.PREFIX;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.LongSummaryStatistics;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
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.SugiyamaLayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.util.InsertionSortCounter;
import org.jungrapht.visualization.layout.algorithms.util.LayeredRunnable;
import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.layout.model.Rectangle;
import org.jungrapht.visualization.layout.util.PropertyLoader;
import org.jungrapht.visualization.layout.util.synthetics.Synthetic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@code Runnable} part of the {@link SugiyamaLayoutAlgorithm} The Sugiyama Hierarchical
* Minimum-Cross layout algorithm
*
* @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"
* @param vertex type
* @param edge type
*/
public class SugiyamaRunnable implements LayeredRunnable {
private static final Logger log = LoggerFactory.getLogger(SugiyamaRunnable.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 SugiyamaRunnable, B extends Builder> {
static {
PropertyLoader.load();
}
protected LayoutModel layoutModel;
protected Function vertexShapeFunction;
protected Predicate vertexPredicate; // can be null
protected Predicate edgePredicate; // can be null
protected Comparator vertexComparator = (v1, v2) -> 0;
protected Comparator edgeComparator = (e1, e2) -> 0;
protected boolean straightenEdges;
protected boolean postStraighten;
protected boolean transpose;
protected int transposeLimit;
protected int maxLevelCross;
protected Layering layering = Layering.TOP_DOWN;
protected boolean multiComponent;
/** {@inheritDoc} */
protected B self() {
return (B) this;
}
/**
* @param vertexPredicate {@link Predicate} to apply to vertices
* @return this Builder
*/
public B vertexPredicate(Predicate vertexPredicate) {
this.vertexPredicate = vertexPredicate;
return self();
}
/**
* @param edgePredicate {@link Predicate} to apply to edges
* @return this Builder
*/
public B edgePredicate(Predicate edgePredicate) {
this.edgePredicate = edgePredicate;
return self();
}
/**
* @param vertexComparator {@link Comparator} to sort vertices
* @return this Builder
*/
public B vertexComparator(Comparator vertexComparator) {
this.vertexComparator = vertexComparator;
return self();
}
/**
* @param edgeComparator {@link Comparator} to sort edges
* @return this Builder
*/
public B edgeComparator(Comparator edgeComparator) {
this.edgeComparator = edgeComparator;
return self();
}
public B layoutModel(LayoutModel layoutModel) {
this.layoutModel = layoutModel;
return self();
}
public B vertexShapeFunction(Function vertexShapeFunction) {
this.vertexShapeFunction = vertexShapeFunction;
return self();
}
public B straightenEdges(boolean straightenEdges) {
this.straightenEdges = straightenEdges;
return self();
}
public B postStraighten(boolean postStraighten) {
this.postStraighten = postStraighten;
return self();
}
public B transpose(boolean transpose) {
this.transpose = transpose;
return self();
}
public B transposeLimit(int transposeLimit) {
this.transposeLimit = transposeLimit;
return self();
}
public B maxLevelCross(int maxLevelCross) {
this.maxLevelCross = maxLevelCross;
return self();
}
public B layering(Layering layering) {
this.layering = layering;
return self();
}
public B multiComponent(boolean multiComponent) {
this.multiComponent = multiComponent;
return self();
}
/** {@inheritDoc} */
public T build() {
return (T) new SugiyamaRunnable<>(this);
}
}
/**
* @param vertex type
* @param edge type
* @return a Builder ready to configure
*/
public static Builder builder() {
return new Builder<>();
}
protected final LayoutModel layoutModel;
protected Function vertexShapeFunction;
protected Graph graph;
protected Graph, LE> svGraph;
protected NeighborCache, LE> neighborCache;
boolean stopit = false;
protected Predicate vertexPredicate;
protected Predicate edgePredicate;
protected Comparator vertexComparator;
protected Comparator edgeComparator;
protected boolean straightenEdges;
protected boolean postStraighten;
protected boolean transpose;
protected int transposeLimit;
protected int maxLevelCross;
protected Layering layering;
protected Map, VertexMetadata> vertexMetadataMap = new HashMap<>();
protected Map> edgePointMap = new HashMap<>();
protected boolean multiComponent;
protected boolean cancelled;
protected SugiyamaRunnable(Builder builder) {
this(
builder.layoutModel,
builder.vertexShapeFunction,
builder.vertexPredicate,
builder.edgePredicate,
builder.vertexComparator,
builder.edgeComparator,
builder.straightenEdges,
builder.postStraighten,
builder.transpose,
builder.transposeLimit,
builder.maxLevelCross,
builder.layering,
builder.multiComponent);
}
private SugiyamaRunnable(
LayoutModel layoutModel,
Function vertexShapeFunction,
Predicate vertexPredicate,
Predicate edgePredicate,
Comparator vertexComparator,
Comparator edgeComparator,
boolean straightenEdges,
boolean postStraighten,
boolean transpose,
int transposeLimit,
int maxLevelCross,
Layering layering,
boolean multiComponent) {
this.layoutModel = layoutModel;
this.vertexShapeFunction = vertexShapeFunction;
this.vertexComparator = vertexComparator;
this.vertexPredicate = vertexPredicate;
this.edgeComparator = edgeComparator;
this.edgePredicate = edgePredicate;
this.straightenEdges = straightenEdges;
this.postStraighten = postStraighten;
this.transpose = transpose;
this.transposeLimit = transposeLimit;
this.maxLevelCross = maxLevelCross;
if (layering == null) {
layering = Layering.TOP_DOWN;
}
this.layering = layering;
this.multiComponent = multiComponent;
}
@Override
public void cancel() {
this.cancelled = true;
}
@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();
this.neighborCache = new NeighborCache<>(svGraph);
long transformTime = System.currentTimeMillis();
log.trace("transform Graph took {}", (transformTime - startTime));
GreedyCycleRemoval, LE> greedyCycleRemoval = new GreedyCycleRemoval(svGraph);
Collection> feedbackArcs = greedyCycleRemoval.getFeedbackArcs();
// 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));
// check for interrupted before layering
if (cancelled || Thread.currentThread().isInterrupted()) {
log.debug("interrupted before layering, cancelled: {}", cancelled);
return;
}
List>> layers;
Comparator> svComparator =
(e1, e2) -> edgeComparator.compare(e1.getEdge(), e2.getEdge());
switch (layering) {
case NETWORK_SIMPLEX:
if (edgeComparator != Layered.noopComparator) {
layers = GraphLayers.networkSimplex(svGraph, svComparator);
} else {
layers = GraphLayers.networkSimplex(svGraph);
}
break;
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, 10, svComparator);
} else {
layers = GraphLayers.coffmanGraham(svGraph, 10);
}
break;
case TOP_DOWN:
default:
if (edgeComparator != Layered.noopComparator) {
layers = GraphLayers.assign(svGraph, svComparator);
} else {
layers = GraphLayers.assign(svGraph);
}
}
long assignLayersTime = System.currentTimeMillis();
log.trace("assign layers took {} ", (assignLayersTime - cycles));
GraphLayers.checkLayers(layers);
Synthetics synthetics = new Synthetics<>(svGraph);
List> edges = new ArrayList<>(svGraph.edgeSet());
LV[][] layersArray = synthetics.createVirtualVerticesAndEdges(edges, layers);
GraphLayers.checkLayers(layersArray);
// save off a map of edge lists keyed on the target vertex rank
Map>> edgesKeyedOnTarget = new LinkedHashMap<>();
edges.forEach(
e -> {
int targetRank = e.getTarget().getRank();
if (edgesKeyedOnTarget.containsKey(targetRank)) {
edgesKeyedOnTarget.get(targetRank).add(e);
} else {
ArrayList> list = new ArrayList<>();
list.add(e);
edgesKeyedOnTarget.put(targetRank, list);
}
});
// save off a map of edge lists keyed on the source vertex rank
Map>> edgesKeyedOnSource = new LinkedHashMap<>();
edges.forEach(
e -> {
int sourceRank = e.getSource().getRank();
if (edgesKeyedOnSource.containsKey(sourceRank)) {
edgesKeyedOnSource.get(sourceRank).add(e);
} else {
ArrayList> list = new ArrayList<>();
list.add(e);
edgesKeyedOnSource.put(sourceRank, list);
}
});
long syntheticsTime = System.currentTimeMillis();
log.trace("synthetics took {}", (syntheticsTime - assignLayersTime));
VertexMetadata[][] vertexMetadata = null;
LV[][] best = null;
int lowestCrossCount = Integer.MAX_VALUE;
// order the ranks
for (int i = 0; i < maxLevelCross; i++) {
if (cancelled || Thread.currentThread().isInterrupted()) {
log.debug("interrupted in level cross, cancelled: {}", cancelled);
return;
}
if (i % 2 == 0) {
medianDownwards(layersArray, svGraph);
if (transpose) transposeDownwards(layersArray, edgesKeyedOnTarget);
} else {
medianUpwards(layersArray, svGraph);
if (transpose) transposeUpwards(layersArray, edgesKeyedOnSource);
}
AllLevelCross allLevelCross = new AllLevelCross<>(svGraph, layersArray);
int allLevelCrossCount = allLevelCross.allLevelCross();
log.trace(" cross count: {}", allLevelCrossCount);
GraphLayers.checkLayers(layersArray);
if (allLevelCrossCount < lowestCrossCount) {
GraphLayers.checkLayers(layersArray);
best = copy(layersArray);
vertexMetadataMap = save(layersArray);
GraphLayers.checkLayers(layersArray);
lowestCrossCount = allLevelCrossCount;
}
}
log.trace("lowest cross count: {}", lowestCrossCount);
restore(layersArray, vertexMetadataMap);
Arrays.stream(layersArray)
.forEach(layer -> Arrays.sort(layer, Comparator.comparingInt(LV::getIndex)));
// compare best and layersArray
if (log.isTraceEnabled()) {
log.trace("best:{}", best);
log.trace("layersArray:{}", layersArray);
}
for (int i = 0; i < best.length; i++) {
LV[] layer = best[i];
for (int j = 0; j < layer.length; j++) {
LV v = layer[j];
if (v.getVertex() != layersArray[i][j].getVertex()) {
log.error("not equal");
}
}
}
// in case zero iterations of cross counting were requested:
long crossCountTests = System.currentTimeMillis();
log.trace("cross counts took {}", (crossCountTests - syntheticsTime));
GraphLayers.checkLayers(layersArray);
// done optimizing for edge crossing
// figure out the avg size of rendered vertex
Rectangle avgVertexBounds = avgVertexBounds(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);
if (cancelled || Thread.currentThread().isInterrupted()) {
log.debug("interrupted before compaction, cancelled: {}", cancelled);
return;
}
if (straightenEdges) {
HorizontalCoordinateAssignment horizontalCoordinateAssignment =
new HorizontalCoordinateAssignment<>(
layersArray, svGraph, new HashSet<>(), horizontalOffset, verticalOffset);
horizontalCoordinateAssignment.horizontalCoordinateAssignment();
GraphLayers.checkLayers(layersArray);
} 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 (int i = 0; i < layersArray.length; i++) {
int width = horizontalOffset;
int maxHeight = 0;
for (int j = 0; j < layersArray[i].length; j++) {
LV v = layersArray[i][j];
if (!(v instanceof Synthetic)) {
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().getAsInt();
int x = horizontalOffset;
int y = verticalOffset;
layerIndex = 0;
log.trace("layerMaxHeights {}", rowMaxHeightMap);
for (int i = 0; i < layersArray.length; i++) {
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 (int j = 0; j < layersArray[i].length; j++) {
LV LV = layersArray[i][j];
int vertexWidth = 0;
if (!(LV instanceof Synthetic)) {
vertexWidth = (int) vertexShapeFunction.apply(LV.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 :
Arrays.stream(layersArray)
.flatMap(Arrays::stream)
.map(LV::getPoint)
.collect(Collectors.toList())) {
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 (LV v : Arrays.stream(layersArray).flatMap(Arrays::stream).collect(Collectors.toList())) {
Point p = v.getPoint();
Point q = Point.of((offsetX + p.x) * scalex, (offsetY + p.y) * scaley);
v.setPoint(q);
}
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.edge))
.forEach(
ae -> {
svGraph.removeEdge(ae);
LE reversed = ae.reversed();
svGraph.addEdge(reversed.getSource(), reversed.getTarget(), reversed);
});
// Map> edgePointMap = new HashMap<>();
for (ArticulatedEdge ae : articulatedEdges) {
List points = new ArrayList<>();
if (feedbackEdges.contains(ae.edge)) {
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()));
}
protected void transposeDownwards(LV[][] ranks, Map>> reducedEdgeMap) {
GraphLayers.checkLayers(ranks);
boolean improved = true;
int sanityCheck = 0;
while (improved) {
improved = false;
for (int i = 0; i < ranks.length; i++) {
LV[] rank = ranks[i];
for (int j = 0; j < rank.length - 1; j++) {
List> biLayerEdges = reducedEdgeMap.getOrDefault(i, Collections.emptyList());
int vw = AccumulatorTreeUtil.crossingCount(biLayerEdges);
if (log.isTraceEnabled()) {
// make sure the accumulator tree count matches the insertion sort count
int vw2 = crossingCount(biLayerEdges);
if (vw != vw2) {
log.error("{} != {}", vw, vw2);
}
int vw3 = AccumulatorTreeUtil.crossingWeight(biLayerEdges, idx -> 1);
if (vw != vw3) {
log.error("{} != {}", vw, vw3);
}
}
if (vw == 0) {
break; // perfect!
}
// count with j and j+1 swapped
swap(rank, j, j + 1);
int wv = AccumulatorTreeUtil.crossingCount(biLayerEdges);
if (log.isTraceEnabled()) {
int wv2 = crossingCount(biLayerEdges);
if (wv != wv2) {
log.error("{} != {}", wv, wv2);
}
int wv3 = AccumulatorTreeUtil.crossingWeight(biLayerEdges, idx -> 1);
if (wv != wv3) {
log.error("{} != {}", wv, wv3);
}
}
swap(rank, j, j + 1);
if (vw > wv) {
improved = true;
swap(rank, j, j + 1);
}
}
}
sanityCheck++;
if (sanityCheck > transposeLimit) {
break;
}
}
GraphLayers.checkLayers(ranks);
}
protected void transposeUpwards(LV[][] ranks, Map>> reducedEdgeMap) {
GraphLayers.checkLayers(ranks);
boolean improved = true;
int sanityLimit = Integer.getInteger(PREFIX + "mincross.transposeLimit", 10);
int sanityCheck = 0;
while (improved) {
improved = false;
for (int i = ranks.length - 1; i >= 0; i--) {
LV[] rank = ranks[i];
for (int j = 0; j < rank.length - 1; j++) {
List> biLayerEdges = reducedEdgeMap.getOrDefault(i, Collections.emptyList());
int vw = AccumulatorTreeUtil.crossingCount(biLayerEdges);
if (log.isTraceEnabled()) {
// make sure the accumulator tree count matches the insertion sort count
int vw2 = crossingCount(biLayerEdges);
if (vw != vw2) {
log.error("{} != {}", vw, vw2);
}
int vw3 = AccumulatorTreeUtil.crossingWeight(biLayerEdges, idx -> 1);
if (vw != vw3) {
log.error("{} != {}", vw, vw3);
}
}
if (vw == 0) {
break; // no crossings. done with these two ranks!
}
// count with j and j+1 swapped
swap(rank, j, j + 1); // swap for comparison
int wv = AccumulatorTreeUtil.crossingCount(biLayerEdges);
if (log.isTraceEnabled()) {
int wv2 = crossingCount(biLayerEdges);
if (wv != wv2) {
log.error("{} != {}", wv, wv2);
}
int wv3 = AccumulatorTreeUtil.crossingWeight(biLayerEdges, idx -> 1);
if (wv != wv3) {
log.error("{} != {}", wv, wv3);
}
}
swap(rank, j, j + 1); // swap back
if (vw > wv) {
improved = true;
swap(rank, j, j + 1); // sawp to improved order
}
}
}
sanityCheck++;
if (sanityCheck > sanityLimit) {
break;
}
}
GraphLayers.checkLayers(ranks);
}
public Map> getEdgePointMap() {
return edgePointMap;
}
private void swap(LV[] array, int i, int j) {
LV temp = array[i];
array[i] = array[j];
array[j] = temp;
array[i].setIndex(i);
array[j].setIndex(j);
}
Comparator> biLevelEdgeComparator = Comparators.biLevelEdgeComparator();
private int crossingCount(List> edges) {
edges.sort(biLevelEdgeComparator);
List targetIndices = new ArrayList<>();
for (LE edge : edges) {
targetIndices.add(edge.getTarget().getIndex());
}
return InsertionSortCounter.insertionSortCounter(targetIndices);
}
private int crossingCountSwapped(int i, int j, LV[] layer, List> edges) {
swap(layer, i, j);
edges.sort(biLevelEdgeComparator);
List targetIndices = new ArrayList<>();
for (LE edge : edges) {
targetIndices.add(edge.getTarget().getIndex());
}
swap(layer, i, j);
return InsertionSortCounter.insertionSortCounter(targetIndices);
}
Function, int[]> upperNeighborIndicesMethod = this::upperNeighborIndices;
Function, int[]> lowerNeighborIndicesMethod = this::lowerNeighborIndices;
//http://www.graphviz.org/Documentation/TSE93.pdf p 15
void median(LV[][] layers, int i, Graph, LE> svGraph) {
if (i % 2 == 0) {
for (int r = 0; r < layers.length; r++) {
for (LV v : layers[r]) {
double median = medianValue(v, upperNeighborIndicesMethod, svGraph);
v.setMeasure(median);
}
medianSortAndFixMetadata(layers[r]);
GraphLayers.checkLayers(layers);
}
} else {
for (int r = layers.length - 1; r >= 0; r--) {
for (LV v : layers[r]) {
double median = medianValue(v, lowerNeighborIndicesMethod, svGraph);
v.setMeasure(median);
}
medianSortAndFixMetadata(layers[r]);
GraphLayers.checkLayers(layers);
}
}
}
protected void medianDownwards(LV[][] layers, Graph, LE> svGraph) {
for (int r = 0; r < layers.length; r++) {
for (LV v : layers[r]) {
double median = medianValue(v, upperNeighborIndicesMethod, svGraph);
v.setMeasure(median);
}
medianSortAndFixMetadata(layers[r]);
GraphLayers.checkLayers(layers);
}
}
protected void medianUpwards(LV[][] layers, Graph, LE> svGraph) {
for (int r = layers.length - 1; r >= 0; r--) {
for (LV v : layers[r]) {
double median = medianValue(v, lowerNeighborIndicesMethod, svGraph);
v.setMeasure(median);
}
medianSortAndFixMetadata(layers[r]);
GraphLayers.checkLayers(layers);
}
}
void medianSortAndFixMetadata(LV[] layer) {
Arrays.sort(layer, Comparator.comparing(LV::getMeasure));
// fix up the metadata!
fixMetadata(layer);
}
private void fixMetadata(LV[] layer) {
for (int idx = 0; idx < layer.length; idx++) {
layer[idx].setIndex(idx);
}
}
int[] upperNeighborIndices(LV vertex) {
return neighborCache.predecessorsOf(vertex).stream().mapToInt(LV::getIndex).sorted().toArray();
}
int[] lowerNeighborIndices(LV vertex) {
return neighborCache.successorsOf(vertex).stream().mapToInt(LV::getIndex).sorted().toArray();
}
int[] adjPosition(
LV v, Function, int[]> neighborFunction, Graph, LE> svGraph) {
return neighborFunction.apply(v);
}
double medianValue(
LV v, Function, int[]> neighborFunction, Graph, LE> svGraph) {
// get the positions of adjacent vertices in adj_rank
int[] P = adjPosition(v, neighborFunction, svGraph);
int m = P.length / 2;
if (P.length == 0) {
return -1;
} else if (P.length % 2 == 1) {
return P[m];
} else if (P.length == 2) {
return (P[0] + P[1]) / 2;
} else {
double left = P[m - 1] - P[0];
double right = P[P.length - 1] - P[m];
return (P[m - 1] * right + P[m] * left) / (left + right);
}
}
double medianValue(int[] P) {
int m = P.length / 2;
if (P.length == 0) {
return -1;
} else if (P.length % 2 == 1) {
return P[m];
} else if (P.length == 2) {
return (P[0] + P[1]) / 2;
} else {
double left = P[m - 1] - P[0];
double right = P[P.length - 1] - P[m];
return (P[m - 1] * right + P[m] * left) / (left + right);
}
}
protected LV[][] copy(LV[][] in) {
LV[][] copy = new LV[in.length][];
for (int i = 0; i < in.length; i++) {
copy[i] = new LV[in[i].length];
for (int j = 0; j < in[i].length; j++) {
copy[i][j] = in[i][j].copy();
}
}
return copy;
}
protected Map, VertexMetadata> save(LV[][] in) {
Map, VertexMetadata> vertexMetadataMap = new HashMap<>();
VertexMetadata[][] saved = new VertexMetadata[in.length][];
for (int i = 0; i < in.length; i++) {
saved[i] = new VertexMetadata[in[i].length];
for (int j = 0; j < in[i].length; j++) {
saved[i][j] = VertexMetadata.of(in[i][j]);
vertexMetadataMap.put(in[i][j], saved[i][j]);
}
}
return vertexMetadataMap;
}
protected LV[][] restore(LV[][] layers, Map, VertexMetadata> vertexMetadataMap) {
for (int i = 0; i < layers.length; i++) {
for (int j = 0; j < layers[i].length; j++) {
VertexMetadata vertexMetadata = vertexMetadataMap.get(layers[i][j]);
vertexMetadata.applyTo(layers[i][j]);
// saved[i][j].applyTo(layers[i][j]);
}
}
return layers;
}
private static Rectangle maxVertexBounds(
List>> layers, Function vertexShapeFunction) {
// figure out the largest rendered vertex
Rectangle maxVertexBounds = Rectangle.IDENTITY;
for (List> list : layers) {
for (LV v : list) {
if (!(v instanceof Synthetic)) {
Rectangle bounds = vertexShapeFunction.apply(v.getVertex());
int width = (int) Math.max(bounds.width, maxVertexBounds.width);
int height = (int) Math.max(bounds.height, maxVertexBounds.height);
maxVertexBounds = Rectangle.of(0, 0, width, height);
}
}
}
return maxVertexBounds;
}
private static Rectangle avgVertexBounds(
LV[][] layers, Function vertexShapeFunction) {
LongSummaryStatistics w = new LongSummaryStatistics();
LongSummaryStatistics h = new LongSummaryStatistics();
for (int i = 0; i < layers.length; i++) {
for (int j = 0; j < layers[i].length; j++) {
if (!(layers[i][j] instanceof Synthetic)) {
Rectangle bounds = vertexShapeFunction.apply(layers[i][j].getVertex());
w.accept((int) bounds.width);
h.accept((int) bounds.height);
}
}
}
return Rectangle.of(0, 0, (int) w.getAverage(), (int) h.getAverage());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy