All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jungrapht.visualization.layout.algorithms.eiglsperger.EiglspergerSteps Maven / Gradle / Ivy

The newest version!
package org.jungrapht.visualization.layout.algorithms.eiglsperger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.jgrapht.Graph;
import org.jgrapht.alg.util.NeighborCache;
import org.jungrapht.visualization.layout.algorithms.sugiyama.AccumulatorTreeUtil;
import org.jungrapht.visualization.layout.algorithms.sugiyama.Comparators;
import org.jungrapht.visualization.layout.algorithms.sugiyama.LE;
import org.jungrapht.visualization.layout.algorithms.sugiyama.LV;
import org.jungrapht.visualization.layout.algorithms.util.InsertionSortCounter;
import org.jungrapht.visualization.layout.algorithms.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The five steps of the Eiglsperger optimization of the Sugiyama Layout Algorithm
 *
 * 

Javadoc text includes descriptions from the Eiglsperger paper * * @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 EiglspergerSteps { private static final Logger log = LoggerFactory.getLogger(EiglspergerSteps.class); /** The delegate Graph to layout */ protected Graph, LE> svGraph; protected NeighborCache, LE> neighborCache; /** the result of layering the graph vertices and introducint synthetic vertices and edges */ protected LV[][] layersArray; /** when sweeping top to bottom, this is a PVertex, bottom to top, this is a QVertex */ protected Predicate> joinVertexPredicate; /** when sweeping top to bottom, this is a QVertex, bottom to top this is a PVertex */ protected Predicate> splitVertexPredicate; protected Function>, List>> edgeEndpointSwapOrNot; /** * When sweeping top to bottom, this function returns predecessors When sweeping bottom to top, * this function returns sucessors */ protected Function, Set>> neighborFunction; protected Function, LV> edgeSourceFunction; protected Function, LV> edgeTargetFunction; protected boolean transpose; protected Graph, Integer> compactionGraph; protected Set> typeOneConflicts = new HashSet<>(); /** * @param svGraph the delegate graph * @param layersArray layered vertices * @param joinVertexPredicate vertices to join with Containers * @param splitVertexPredicate vertices to split from Containers * @param neighborFunction predecessors or successors in the Graph */ protected EiglspergerSteps( Graph, LE> svGraph, LV[][] layersArray, Predicate> joinVertexPredicate, Predicate> splitVertexPredicate, Function, LV> edgeSourceFunction, Function, LV> edgeTargetFunction, Function, Set>> neighborFunction, Function>, List>> edgeEndpointSwapOrNot, boolean transpose) { this.svGraph = svGraph; this.neighborCache = new NeighborCache<>(svGraph); this.layersArray = layersArray; this.joinVertexPredicate = joinVertexPredicate; this.splitVertexPredicate = splitVertexPredicate; this.edgeSourceFunction = edgeSourceFunction; this.edgeTargetFunction = edgeTargetFunction; this.neighborFunction = neighborFunction; this.edgeEndpointSwapOrNot = edgeEndpointSwapOrNot; this.transpose = transpose; } /** * formatted output * * @param label identifier * @param list vertices to log */ private void log(String label, List> list) { log.info(label); list.forEach(v -> log.info(" - {}", v.toString())); } protected static void clearGraph(Graph graph) { Set edges = new HashSet<>(graph.edgeSet()); Set vertices = new HashSet<>(graph.vertexSet()); graph.removeAllEdges(edges); graph.removeAllVertices(vertices); } public Set> getTypeOneConflicts() { return typeOneConflicts; } /** * formatted output * * @param label identifier * @param array vertices to log */ private void log(String label, LV[] array) { log.info(label); Arrays.stream(array).forEach(v -> log.info(" - {}", v.toString())); } /** * "In the first step we append the segment s(v) for each p-vertex v in layer L i to the container * preceding v. Then we join this container with the succeeding container. The result is again an * alternating layer (p-vertices are omitted). for any PVertex (QVertex) that is in the list, take * that vertex's segment and append it to the any prior Container in the list (creating the * Container as needed), and do not append the PVertex (QVertex) in the list to be returned. * Finally, scan the list to join any sequential Containers into one and to insert empty * Containers between sequential vertices. * * @param currentLayer the rank of vertices to operate over * @return layerI modified so that PVertices are gone (added to previous containers) */ public void stepOne(List> currentLayer) { if (log.isTraceEnabled()) log("stepOne currentLayer in", currentLayer); List> outList = new ArrayList<>(); for (LV v : currentLayer) { // for each PVertex/QVertex, add it to the list's adjacent container if (joinVertexPredicate.test(v)) { if (outList.isEmpty()) { outList.add(Container.createSubContainer()); } Container lastContainer = (Container) outList.get(outList.size() - 1); SegmentVertex segmentVertex = (SegmentVertex) v; Segment segment = segmentVertex.getSegment(); lastContainer.append(segment); } else { outList.add(v); } } List> scannedList = EiglspergerUtil.scan(outList); currentLayer.clear(); currentLayer.addAll(scannedList); IntStream.range(0, currentLayer.size()).forEach(i -> currentLayer.get(i).setIndex(i)); if (log.isTraceEnabled()) log("stepOne currentLayer out (merged pvertices into containers)", currentLayer); } private static void updateIndices(List> layer) { IntStream.range(0, layer.size()).forEach(i -> layer.get(i).setIndex(i)); } /** * "In the second step we compute the measure values for the elements in L i+1 . First we assign a * position value pos(v i j ) to all vertices v i j in L i . pos(v i 0 ) = size(S i 0 ) and pos(v * i j ) = pos(v i j−1 ) + size(S i j ) + 1. Note that the pos values are the same as they would * be in the median or barycenter heuristic if each segment was represented as dummy vertex. Each * non- empty container S i j has pos value pos(v i j −1 ) + 1. If container S i 0 is non- empty * it has pos value 0. Now we assign the measure to all non-q-vertices and containers in L i+1 . * The initial containers in L i+1 are the resulting containers of the first step. Recall that the * measure of a container in L i+1 is its position in L i ." Assign positions to the * currentLayerVertices and use those posisions to calculate the measure for vertices in the * downstreamLayer. The measure here is the median of the positions of neghbor vertices * * @param currentLayer * @param downstreamLayer */ public void stepTwo(List> currentLayer, List> downstreamLayer) { if (log.isTraceEnabled()) log("stepTwo currentLayer in", currentLayer); if (log.isTraceEnabled()) log("stepTwo downstreamLayer in", downstreamLayer); assignPositions(currentLayer); if (updatePositions(currentLayer)) { log.error("positions were off for {}", currentLayer); } List> containersFromCurrentLayer = currentLayer .stream() .filter(v -> v instanceof Container) .map(v -> (Container) v) .filter(c -> c.size() > 0) .collect(Collectors.toList()); // add to downstreamLayer, any currentLayer containers that are not already present containersFromCurrentLayer .stream() .filter(c -> !downstreamLayer.contains(c)) .forEach(downstreamLayer::add); assignMeasures(downstreamLayer); if (log.isTraceEnabled()) log("stepTwo currentLayer out (computed pos for currentLayer)", currentLayer); if (log.isTraceEnabled()) log("stepTwo downstreamLayer out (computed measures for downstreamLayer)", downstreamLayer); } /** * "In the third step we calculate an initial ordering of L i+1 . We sort all non-q-vertices in L * i+1 according to their measure in a list L V . We do the same for the containers and store them * in a list L S . We use the following operations on these sorted lists:" * *

    *
  • ◦ l = pop(L) : Removes the first element l from list L and returns it. *
  • ◦ push(L, l) : Inserts element l at the head of list L. *
* * We merge both lists in the following way: * if m(head(L V )) ≤ pos(head(L S )) * then v = pop(L V ), append(L i+1 , v) * if m(head(L V )) ≥ (pos(head(L S )) + size(head(L S )) − 1) * then S = pop(L S ), append(L i+1 , S) * else S = pop(L S ), v = pop(L V ), k = ⌈m(v) − pos(S)⌉, * (S 1 ,S 2 ) = split(S, k), append(L i+1 ,S 1 ), append(L i+1 , v), * pos(S 2 ) = pos(S) + k, push(L S ,S 2 ). * * * @param downstreamLayer */ public void stepThree(List> downstreamLayer) { if (log.isTraceEnabled()) log("stepThree downstreamLayer in", downstreamLayer); List> listV = new LinkedList<>(); List> listS = new LinkedList<>(); List> segmentVertexList = new ArrayList<>(); for (LV v : downstreamLayer) { if (splitVertexPredicate.test(v)) { // skip any QVertex for top to bottom segmentVertexList.add((SegmentVertex) v); } else if (v instanceof Container) { Container container = (Container) v; if (container.size() > 0) { listS.add(container); } } else { listV.add(v); } } // sort the list by elements measures if (log.isTraceEnabled()) { log.trace("listS measures: {}", listS); } if (log.isTraceEnabled()) { log.trace("listV measures: {}", listV); } try { listS.sort(Comparator.comparingDouble(Container::getMeasure)); listV.sort(Comparator.comparingDouble(LV::getMeasure)); if (log.isTraceEnabled()) { StringBuilder sbuilder = new StringBuilder("S3 listS:\n"); listS.forEach(s -> sbuilder.append(s.toString()).append("\n")); log.trace(sbuilder.toString()); StringBuilder vbuilder = new StringBuilder("S3 listV:\n"); listV.forEach(v -> vbuilder.append(v.toString()).append("\n")); log.trace(vbuilder.toString()); } } catch (Exception ex) { log.error("listS: {}, listV: {} exception: {}", listS, listV, ex); } /* if the measure of the head of the vertex list LV is <= position of the head of the container list LS, then pop the vertex from the vertex list and append it to the Li+1 list if the measure of the head of the vertex list is >= (position of the head of the container list + size of the head of the container list - 1) then pop the head of the container list and append it to the Li+1 list else pop S the first container and v the first vertex from their lists k = ceiling(measure(v)-pos(S)) split S at k into S1, S2 append S1 to the Li+1 list, append v to the L+1 list, set pos(S2) to be pos(S) + k, push S2 onto container list LS */ List> mergedList = new ArrayList<>(); if (listS.isEmpty() || listV.isEmpty()) { mergedList.addAll(listS); mergedList.addAll(listV); mergedList.sort(Comparator.comparingDouble(LV::getMeasure)); } else { while (!listV.isEmpty() && !listS.isEmpty()) { if (listV.get(0).getMeasure() <= listS.get(0).getPos()) { LV v = listV.remove(0); mergedList.add(v); } else if (listV.get(0).getMeasure() >= (listS.get(0).getPos() + listS.get(0).size() - 1)) { Container container = listS.remove(0); mergedList.add(container); } else { Container container = listS.remove(0); LV v = listV.remove(0); int k = (int) Math.ceil(v.getMeasure() - container.getPos()); if (log.isTraceEnabled()) log.trace("will split {} at {}", container, k); Pair> containerPair = Container.split(container, k); if (log.isTraceEnabled()) log.trace("got {} and {}", containerPair.first, containerPair.second); mergedList.add(containerPair.first); mergedList.add(v); int pos = container.getPos() + k; containerPair.second.setPos(pos); listS.add(0, containerPair.second); } } // add any leftovers to listPlusOne mergedList.addAll(listV); mergedList.addAll(listS); } mergedList.addAll(segmentVertexList); if (log.isTraceEnabled()) { StringBuilder builder = new StringBuilder("S3 mergedList:\n"); mergedList.forEach(v -> builder.append(v.toString()).append("\n")); log.trace(builder.toString()); } downstreamLayer.clear(); downstreamLayer.addAll(mergedList); // fix the indices updateIndices(downstreamLayer); if (updatePositions(downstreamLayer)) { log.trace("positions were updated for {}", downstreamLayer); } if (log.isTraceEnabled()) log("stepThree downstreamLayer out (initial ordering for downstreamLayer)", downstreamLayer); } /** * In the fourth step we place each q-vertex v of L i+1 according to the position of its * corresponding segment s(v). We do this by calling split(S, s(v)) for each q-vertex v in layer L * i+1 and placing v between the resulting containers (S denotes the container that includes * s(v)). * * @param downstreamLayer * @param downstreamRank */ public void stepFour(List> downstreamLayer, int downstreamRank) { if (log.isTraceEnabled()) log("stepFour downstreamLayer in", downstreamLayer); // for each qVertex, get its Segment, find the segment in one of the containers in downstreamLayer // gather the qVertices List> qVertices = downstreamLayer .stream() .filter(v -> splitVertexPredicate.test(v)) // QVertices .map(v -> (SegmentVertex) v) .collect(Collectors.toList()); for (SegmentVertex q : qVertices) { List> containerList = downstreamLayer .stream() .filter(v -> v instanceof Container) .map(v -> (Container) v) .collect(Collectors.toList()); // find its container Segment segment = q.getSegment(); Optional> containerOpt = containerList.stream().filter(c -> c.contains(segment)).findFirst(); if (containerOpt.isPresent()) { Container container = containerOpt.get(); int loserIdx = downstreamLayer.indexOf(container); if (log.isTraceEnabled()) { log.trace( "found container {} at index {} with list index {} for qVertex {} with index {} and list index {}", container, container.getIndex(), loserIdx, q, q.getIndex(), downstreamLayer.indexOf(q)); log.trace("splitting on {} because of {}", segment, q); } Pair> containerPair = Container.split(container, segment); if (log.isTraceEnabled()) { log.trace( "splitFound container into {} and {}", containerPair.first, containerPair.second); log.trace( "container pair is now {} and {}", containerPair.first.printTree("\n"), containerPair.second.printTree("\n")); } downstreamLayer.remove(q); if (log.isTraceEnabled()) { log.trace("removed container {}", container.printTree("\n")); log.trace("adding container {}", containerPair.first.printTree("\n")); log.trace("adding container {}", containerPair.second.printTree("\n")); } downstreamLayer.remove(container); downstreamLayer.add(loserIdx, containerPair.first); downstreamLayer.add(loserIdx + 1, q); downstreamLayer.add(loserIdx + 2, containerPair.second); } else { log.error("container opt was empty for segment {}", segment); } } updateIndices(downstreamLayer); updatePositions(downstreamLayer); // IntStream.range(0, downstreamLayer.size()).forEach(i -> downstreamLayer.get(i).setIndex(i)); Arrays.sort(layersArray[downstreamRank], Comparator.comparingInt(LV::getIndex)); if (log.isTraceEnabled()) log("stepFour downstreamLayer out (split containers for Q/PVertices)", downstreamLayer); if (log.isTraceEnabled()) log("layersArray[" + downstreamRank + "] out", layersArray[downstreamRank]); } /** * In the fifth step we perform cross counting according to the scheme pro- posed by Barth et al * (see Section 1.2). During the cross counting step between layer L i and L i+1 we therefore * consider all layer elements as ver- tices. Beside the common edges between both layers, we also * have to handle virtual edges, which are imaginary edges between a container ele- ment in L i * and the resulting container elements or q-vertices in L i+1 (see Figure 5). In terms of the * common approach each virtual edge represents at least one edge between two dummy vertices. The * number of represented edges is equal to the size of the container element in L i+1 . We have to * consider this fact to get the right number of edge crossings. We therefore introduce edge * weights. The weight of a virtual edge ending with a con- tainer element S is equal to size(S). * The weight of the other edges is one. So a crossing between two edges e 1 and e 2 counts as * weight(e 1 )·weight(e 2 ) crossings. * * @param currentLayer the Li layer * @param downstreamLayer the Li+1 (or Li-1 for backwards) layer * @param currentRank the value of i for Li * @param downstreamRank the value of i+1 (or i-1 for backwards) * @return count of edge crossing weight */ public int stepFive( List> currentLayer, List> downstreamLayer, int currentRank, int downstreamRank) { return transpose(currentLayer, downstreamLayer, currentRank, downstreamRank); } private int transpose( List> currentLayer, List> downstreamLayer, int currentRank, int downstreamRank) { // gather all the graph edges between the currentRank and the downstreamRank List> biLayerEdges = svGraph .edgeSet() .stream() .filter( e -> edgeSourceFunction.apply(e).getRank() == currentRank && edgeTargetFunction.apply(e).getRank() == downstreamRank) .collect(Collectors.toList()); // create virtual edges between non-empty containers in both ranks // if the downstreamLayer has a QVertex/PVertex, create a virtual edge between a new synthetic vertex // in currentLayer and the QVertex/PVertex in the downstreamLayer Set> virtualEdges = new HashSet<>(); for (LV v : downstreamLayer) { if (v instanceof Container) { Container container = (Container) v; if (container.size() > 0) { virtualEdges.add(VirtualEdge.of(container, container)); } } else if (splitVertexPredicate.test(v)) { // downwards, this is a QVertex, upwards its a PVertex SegmentVertex qv = (SegmentVertex) v; SyntheticLV qvSource = SyntheticLV.of(); qvSource.setIndex(qv.getIndex()); qvSource.setPos(qv.getPos()); virtualEdges.add(VirtualEdge.of(qvSource, qv)); } } for (Iterator> iterator = currentLayer.iterator(); iterator.hasNext(); ) { if (isEmptyContainer(iterator.next())) { iterator.remove(); } } updateIndices(currentLayer); updatePositions(currentLayer); // remove any empty containers from the downstreamLayer and reset the index metadata // for the currentLayer vertices for (Iterator> iterator = downstreamLayer.iterator(); iterator.hasNext(); ) { if (isEmptyContainer(iterator.next())) { iterator.remove(); } } updateIndices(downstreamLayer); updatePositions(downstreamLayer); typeOneConflicts.addAll(this.getEdgesThatCrossVirtualEdge(virtualEdges, biLayerEdges)); biLayerEdges.addAll(virtualEdges); // downwards, the function is a no-op, upwards the biLayerEdges endpoints are swapped if (log.isTraceEnabled()) { log.trace("for ranks {} and {} ....", currentRank, downstreamRank); } return processRanks(downstreamLayer, edgeEndpointSwapOrNot.apply(biLayerEdges)); } private int processRanks(List> downstreamLayer, List> biLayerEdges) { int crossCount = Integer.MAX_VALUE; // define a function that will get the edge weight from its target vertex // in the downstream layer. If the target is a container, it's weight is // the size of the container Function f = i -> { LE edge = biLayerEdges.get(i); LV target = edge.getTarget(); if (target instanceof Container) { return ((Container) target).size(); } return 1; }; if (downstreamLayer.size() < 2) { crossCount = 0; } for (int j = 0; j < downstreamLayer.size() - 1; j++) { // if either of the adjacent vertices is a container, skip them if (log.isTraceEnabled()) { // runs the crossingCount (no weights) with the insertionSort method and the AccumulatorTree method // these values should match and should both be <= to the crossingWeight int vw2 = crossingCount(biLayerEdges); int vw3 = AccumulatorTreeUtil.crossingCount(biLayerEdges); if (log.isTraceEnabled()) { log.trace("IS count:{}, AC count:{}", vw2, vw3); } } int vw = AccumulatorTreeUtil.crossingWeight(biLayerEdges, f); crossCount = Math.min(vw, crossCount); if (log.isTraceEnabled()) { log.trace("crossingWeight:{}", vw); } if (vw == 0) { // can't do better than zero break; } if (downstreamLayer.get(j).getMeasure() != downstreamLayer.get(j + 1).getMeasure()) { continue; } // count with j and j+1 swapped // first swap them swap(downstreamLayer, j, j + 1); if (log.isTraceEnabled()) { // runs the crossingCount (no weights) with the insertionSort method and the AccumulatorTree method // these values should match and should both be <= to the crossingWeight int wv2 = crossingCount(biLayerEdges); int wv3 = AccumulatorTreeUtil.crossingCount(biLayerEdges); log.trace("IS count:{}, AC count:{}", wv2, wv3); } int wv = AccumulatorTreeUtil.crossingWeight(biLayerEdges, f); crossCount = Math.min(wv, crossCount); if (log.isTraceEnabled()) { log.trace("swapped crossingWeight:{}", wv); } // put them back unswapped swap(downstreamLayer, j, j + 1); if (vw > wv) { // if the swapped weight is lower, swap them and save off the better swap(downstreamLayer, j, j + 1); if (wv == 0) { break; } } } log.trace("crossCount {}", crossCount); updatePositions(downstreamLayer); return crossCount; } Set> getEdgesThatCrossVirtualEdge( Set> virtualEdges, List> biLayerEdges) { Set virtualEdgeIndices = new HashSet<>(); for (LE edge : virtualEdges) { virtualEdgeIndices.add(edge.getSource().getIndex()); virtualEdgeIndices.add(edge.getTarget().getIndex()); } Set> typeOneConflictEdges = new HashSet<>(); for (LE edge : biLayerEdges) { if (edge instanceof VirtualEdge) continue; List sortedIndices = new ArrayList<>(); sortedIndices.add(edge.getSource().getIndex()); sortedIndices.add(edge.getTarget().getIndex()); Collections.sort(sortedIndices); for (int virtualIndex : virtualEdgeIndices) { int idxZero = sortedIndices.get(0); int idxOne = sortedIndices.get(1); if (idxZero <= virtualIndex && virtualIndex < idxOne) { typeOneConflictEdges.add(edge); } } } return typeOneConflictEdges; } private boolean isEmptyContainer(LV v) { return v instanceof Container && ((Container) v).size() == 0; } protected static List> swapEdgeEndpoints(List> list) { return list.stream() .map(e -> LE.of(e.getEdge(), e.getTarget(), e.getSource())) .collect(Collectors.toList()); // return list.stream().map(LE::swapped).collect(Collectors.toList()); } private int crossingCount(List> edges) { edges.sort(Comparators.biLevelEdgeComparator()); List targetIndices = new ArrayList<>(); for (LE edge : edges) { targetIndices.add(edge.getTarget().getIndex()); } return InsertionSortCounter.insertionSortCounter(targetIndices); } private static 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); } private static void swap(List> array, int i, int j) { Collections.swap(array, i, j); array.get(i).setIndex(i); array.get(j).setIndex(j); updatePositions(array); } /** * In the sixth step we perform a scan on L i+1 and insert empty containers between two * consecutive vertices, and call join(S 1 , S 2 ) on two consecutive containers in the list. This * ensures that L i+1 is an alternating layer. * * @param downstreamLayer */ public void stepSix(List> downstreamLayer) { if (log.isTraceEnabled()) log("stepSix downstreamLayer in", downstreamLayer); List> scanned = EiglspergerUtil.scan(downstreamLayer); downstreamLayer.clear(); downstreamLayer.addAll(scanned); if (log.isTraceEnabled()) log("stepSix downstreamLayer out (padded with and compressed containers)", downstreamLayer); } /** * return the segment to which v is incident, if v is a PVertex or a QVertex. Otherwise, return v * * @param v the vertex to get a segment for * @param vertex type * @return the segment for v or else v */ static LV s(LV v) { if (v instanceof SegmentVertex) { SegmentVertex pVertex = (SegmentVertex) v; return pVertex.getSegment(); } else { return v; } } /** * update the positions so that the preceding container size is used * * @param layer */ static boolean updatePositions(List> layer) { boolean changed = false; int currentPos = 0; for (LV v : layer) { if (v instanceof Container && ((Container) v).size() == 0) { continue; } if (v.getPos() != currentPos) { changed = true; } v.setPos(currentPos); if (v instanceof Container) { currentPos += ((Container) v).size(); } else { currentPos++; } } return changed; } void assignPositions(List> currentLayer) { LV previousVertex = null; Container previousContainer = null; for (int i = 0; i < currentLayer.size(); i++) { LV v = currentLayer.get(i); if (i % 2 == 0) { // this is a container Container container = (Container) v; if (container.size() > 0) { if (previousContainer == null) { // first container non empty container.setPos(0); } else { // there has to be a previousVertex int pos = previousVertex.getPos() + 1; container.setPos(pos); } } previousContainer = container; } else { // this is a vertex if (previousVertex == null) { // first vertex (position 1) int pos = previousContainer.size(); v.setPos(pos); } else { int pos = previousVertex.getPos() + previousContainer.size() + 1; v.setPos(pos); } previousVertex = v; } } } void assignMeasures(List> downstreamLayer) { downstreamLayer .stream() .filter(v -> v instanceof Container) .map(v -> (Container) v) .filter(c -> c.size() > 0) .forEach( c -> { double measure = c.getPos(); c.setMeasure(measure); }); for (LV v : downstreamLayer) { if (splitVertexPredicate.test(v)) { // QVertex for top to bottom continue; } if (v instanceof Container) { Container container = (Container) v; double measure = container.getPos(); container.setMeasure(measure); } else { // not a container (nor QVertex for top to bottom) // measure will be related to the median of the pos of predecessor vert Set> neighbors = neighborFunction.apply(v); int[] poses = new int[neighbors.size()]; int i = 0; for (LV neighbor : neighbors) { poses[i++] = neighbor.getPos(); } // IntStream.range(0, poses.length).forEach(idx -> poses[idx] = neighbors.get(idx).getPos()); if (poses.length > 0) { int measure = medianValue(poses); // poses will be sorted in medianValue method v.setMeasure(measure); } else { // leave the measure as as the current pos if (v.getPos() < 0) { log.debug("no pos for {}", v); } double measure = v.getPos(); v.setMeasure(measure); } } } } /** * return the median value in the array P (which is Sorted!) * * @param P a sorted array * @return the median value */ static int medianValue(int[] P) { if (P.length == 0) { return -1; } else if (P.length == 1) { return P[0]; } Arrays.sort(P); int m = P.length / 2; if (P.length % 2 == 1) { return P[m]; } else if (P.length == 2) { return (P[0] + P[1]) / 2; } else { int left = P[m - 1] - P[0]; int right = P[P.length - 1] - P[m]; if (left + right == 0) { return 0; } return (P[m - 1] * right + P[m] * left) / (left + right); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy