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

org.jungrapht.visualization.layout.algorithms.EdgeAwareTreeLayoutAlgorithm Maven / Gradle / Ivy

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

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jgrapht.Graph;
import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.layout.model.Rectangle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A {@link TreeLayoutAlgorithm} that can evaluate {@code Comparator} and {@link Predicate} for both
 * edges and vertices.
 *
 * 

During graph traversal (for tree layout) the Comparators may be spcified to help order the * traversal to favor certain edges or vertices. Similarly, the Predicates may be used to help * follow a desired path to a vertex when multiple alternatives appear in the graph. * * @author Tom Nelson */ public class EdgeAwareTreeLayoutAlgorithm extends TreeLayoutAlgorithm implements TreeLayout, EdgeAwareLayoutAlgorithm, EdgeSorting, EdgePredicated, VertexSorting, VertexPredicated { private static final Logger log = LoggerFactory.getLogger(EdgeAwareTreeLayoutAlgorithm.class); /** * a Builder to create a configured instance of an EdgeAwareTreeLayoutAlgorithm * * @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 EdgeAwareTreeLayoutAlgorithm, B extends Builder> extends TreeLayoutAlgorithm.Builder implements EdgeAwareLayoutAlgorithm.Builder { protected Predicate vertexPredicate = v -> false; protected Predicate edgePredicate = e -> false; protected Comparator vertexComparator = (v1, v2) -> 0; protected Comparator edgeComparator = (e1, e2) -> 0; protected boolean alignFavoredEdges = true; /** {@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 alignFavoredEdges(boolean alignFavoredEdges) { this.alignFavoredEdges = alignFavoredEdges; return self(); } /** {@inheritDoc} */ public T build() { return (T) new EdgeAwareTreeLayoutAlgorithm<>(this); } } /** * @param vertex type * @param edge type * @return a Builder ready to configure */ public static Builder edgeAwareBuilder() { return new Builder<>(); } public EdgeAwareTreeLayoutAlgorithm() { this(EdgeAwareTreeLayoutAlgorithm.edgeAwareBuilder()); } /** * Create an instance with the passed builder parameters * * @param builder the configuration */ protected EdgeAwareTreeLayoutAlgorithm(Builder builder) { super(builder); this.vertexPredicate = builder.vertexPredicate; this.edgePredicate = builder.edgePredicate; this.vertexComparator = builder.vertexComparator; this.edgeComparator = builder.edgeComparator; this.alignFavoredEdges = builder.alignFavoredEdges; } /** a {@link Predicate} to filter vertices */ protected Predicate vertexPredicate; /** a {@link Predicate} to filter edges */ protected Predicate edgePredicate; /** a {@link Comparator} to sort vertices */ protected Comparator vertexComparator; /** a {@link Comparator} to sort edges */ protected Comparator edgeComparator; protected boolean alignFavoredEdges; /** @param vertexPredicate parameter to set */ @Override public void setVertexPredicate(Predicate vertexPredicate) { this.vertexPredicate = vertexPredicate; } /** @param edgePredicate parameter to set */ @Override public void setEdgePredicate(Predicate edgePredicate) { this.edgePredicate = edgePredicate; } /** @param vertexComparator parameter to set */ @Override public void setVertexComparator(Comparator vertexComparator) { if (vertexComparator == null) { vertexComparator = (v1, v2) -> 0; } this.vertexComparator = vertexComparator; } /** @param edgeComparator parameter to set */ @Override public void setEdgeComparator(Comparator edgeComparator) { if (edgeComparator == null) { edgeComparator = (e1, e2) -> 0; } this.edgeComparator = edgeComparator; } /** * @param layoutModel the model to hold vertex positions * @return the roots vertices of the tree */ protected Set buildTree(LayoutModel layoutModel) { Set roots; Graph graph = layoutModel.getGraph(); if (graph == null || graph.vertexSet().isEmpty()) { correctOverlap = expandLayout = false; return Collections.emptySet(); } if (graph.vertexSet().size() == 1) { V loner = graph.vertexSet().stream().findFirst().get(); layoutModel.set(loner, layoutModel.getWidth() / 2, layoutModel.getHeight() / 2); correctOverlap = expandLayout = false; return graph.vertexSet(); } if (alignFavoredEdges) { // builds without expanding roots = alignBuildTree(layoutModel); } else { roots = super.buildTree(layoutModel); } return roots; } /** * Build the tree (position the vertices) starting at the passed vertex and then descending * recursively into its child vertices * * @param layoutModel the {@link LayoutModel} to hold the vertex positions * @param vertex the vertex to place in position * @param x the x position * @param y the y position * @param seen a set of vertices that were already 'seen' (and placed in the layoutModel) */ protected void buildTree(LayoutModel layoutModel, V vertex, int x, int y, Set seen) { if (seen.add(vertex)) { Graph graph = layoutModel.getGraph(); log.trace("buildTree placing {}", vertex); // go one level further down y += this.verticalVertexSpacing; log.trace("Set vertex {} to {}", vertex, Point.of(x, y)); layoutModel.set(vertex, x, y); merge(layoutModel, vertex); double sizeXofCurrent = baseBounds.get(vertex).width; x -= sizeXofCurrent / 2; for (E edge : graph .outgoingEdgesOf(vertex) .stream() .sorted(edgeComparator) .collect(Collectors.toCollection(LinkedHashSet::new))) { if (edgePredicate.test(edge) || graph.incomingEdgesOf(graph.getEdgeTarget(edge)).stream().noneMatch(edgePredicate)) { V v = graph.getEdgeTarget(edge); if (!rootPredicate.test(v) && !seen.contains(v)) { double sizeXofChild = this.baseBounds.getOrDefault(v, Rectangle.IDENTITY).width; x += sizeXofChild / 2; buildTree(layoutModel, v, x, y, seen); merge(layoutModel, v); x += sizeXofChild / 2 + horizontalVertexSpacing; } } } } } /** * Calculate the overall width of the subtree rooted at the passed vertex. The edgePredicate is * used to keep favored edges together. For example, if a non-favored edge provides a vertex * sooner than its favored edge, the non-favored edge path will be deferred until the favored edge * provides the same vertex. This prevents edges that span from one tree to another from * 'stealing' a subtree that we would prefer to keep together on its favored edge path * * @param layoutModel the source of the Graph and its vertices * @param vertex the vertex at the top of the current subtree * @param seen a set of vertices that were already counted * @return the overall width of the subtree rooted at the passed vertex */ protected int calculateWidth(LayoutModel layoutModel, V vertex, Set seen) { if (seen.add(vertex)) { Graph graph = layoutModel.getGraph(); int width = Math.max( 0, graph .outgoingEdgesOf(vertex) .stream() .sorted(edgeComparator) // skip over any edge that is not in the edgePredicate but also has a target with an // incoming edge that is in the edgePredicate .filter( e -> edgePredicate.test(e) || graph .incomingEdgesOf(graph.getEdgeTarget(e)) .stream() .noneMatch(edgePredicate)) .map(graph::getEdgeTarget) .filter(v -> !rootPredicate.test(v) && !seen.contains(v)) .mapToInt( element -> calculateWidth(layoutModel, element, seen) + horizontalVertexSpacing) .sum() - horizontalVertexSpacing); log.trace("calcWidth baseWidths put {} {}", vertex, width); baseBounds.merge( vertex, Rectangle.of(0, 0, width, 0), (r, t) -> Rectangle.of(r.x, r.y, t.width, r.height)); return width; } return 0; } /** * Calculate the overall height of the subtree rooted at the passed vertex. The edgePredicate is * used to keep favored edges together. For example, if a non-favored edge provides a vertex * sooner than its favored edge, the non-favored edge path will be deferred until the favored edge * provides the same vertex. This prevents edges that span from one tree to another from * 'stealing' a subtree that we would prefer to keep together on its favored edge path * * @param layoutModel the source of the Graph and its vertices * @param vertex the vertex at the top of the current subtree * @param seen a set of vertices that were already counted * @return the overall height of the subtree rooted at the passed vertex */ protected int calculateHeight(LayoutModel layoutModel, V vertex, Set seen) { if (seen.add(vertex)) { Graph graph = layoutModel.getGraph(); int height = graph .outgoingEdgesOf(vertex) .stream() .sorted(edgeComparator) // skip over any edge that is not in the edgePredicate but also has a target with an // incoming edge that is in the edgePredicate .filter( e -> edgePredicate.test(e) || graph .incomingEdgesOf(graph.getEdgeTarget(e)) .stream() .noneMatch(edgePredicate)) .map(graph::getEdgeTarget) .filter(v -> !rootPredicate.test(v) && !seen.contains(v)) .mapToInt( element -> calculateHeight(layoutModel, element, seen) + verticalVertexSpacing) .max() .orElse(0); baseBounds.merge( vertex, Rectangle.of(0, 0, 0, height), (r, t) -> Rectangle.of(r.x, r.y, r.width, t.height)); return height; } return 0; } /** * After the tree is configured, visit all of the vertices that are on favored edges and adjust * their position to the left side of their children's bounding box. This helps provide a more * linear path for favored edge endpoints * * @param layoutModel the source of the graph and its vertices * @return the Set of root vertices */ protected Set alignBuildTree(LayoutModel layoutModel) { Set roots = super.buildTree(layoutModel); Graph graph = layoutModel.getGraph(); // move all the predicated vertices or vertices with adjacent predicated edges for (V vertex : graph.vertexSet()) { if (vertexPredicate.test(vertex) || graph.outgoingEdgesOf(vertex).stream().anyMatch(edgePredicate) || graph.incomingEdgesOf(vertex).stream().anyMatch(edgePredicate)) { Rectangle vertexRectangle = baseBounds.getOrDefault(vertex, Rectangle.IDENTITY); layoutModel.set(vertex, vertexRectangle.x, vertexRectangle.y); } } return roots; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy