org.eclipse.elk.alg.layered.intermediate.compaction.LGraphToCGraphTransformer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.alg.layered.intermediate.compaction.LGraphToCGraphTransformer.java

Source

/*******************************************************************************
 * Copyright (c) 2016 Kiel University and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kiel University - initial API and implementation
 *******************************************************************************/
package org.eclipse.elk.alg.layered.intermediate.compaction;

import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.elk.alg.layered.compaction.oned.CGraph;
import org.eclipse.elk.alg.layered.compaction.oned.CGroup;
import org.eclipse.elk.alg.layered.compaction.oned.CNode;
import org.eclipse.elk.alg.layered.compaction.oned.CompareFuzzy;
import org.eclipse.elk.alg.layered.compaction.oned.ICGraphTransformer;
import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LGraph;
import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LNode.NodeType;
import org.eclipse.elk.alg.layered.graph.Layer;
import org.eclipse.elk.alg.layered.properties.InternalProperties;
import org.eclipse.elk.alg.layered.properties.LayeredOptions;
import org.eclipse.elk.core.math.KVector;
import org.eclipse.elk.core.options.Direction;
import org.eclipse.elk.core.options.PortSide;
import org.eclipse.elk.core.util.Pair;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * Manages the transformation of {@link LNode}s and {@link LEdge}s from an {@link LGraph} into
 * compactable {@link CNode}s and {@link CGroup}s.
 */
public final class LGraphToCGraphTransformer implements ICGraphTransformer<LGraph> {

    /** the output CGraph. */
    private CGraph cGraph;
    /** the layered graph. */
    private LGraph layeredGraph;
    /** flags the input graph if it has edges. */
    private boolean hasEdges;
    /** remember comment boxes as we neglect them during compaction and offset them afterwards. */
    private Map<LNode, Pair<LNode, KVector>> commentOffsets = Maps.newHashMap();

    /**
     * {@inheritDoc}
     */
    @Override
    public CGraph transform(final LGraph inputGraph) {

        layeredGraph = inputGraph;
        commentOffsets.clear();

        // checking if the graph has edges and possibly prohibiting vertical compaction
        hasEdges = false;
        outer: for (Layer l : layeredGraph) {
            for (LNode n : l) {
                if (!Iterables.isEmpty(n.getConnectedEdges())) {
                    hasEdges = true;
                    break outer;
                }
            }
        }
        EnumSet<Direction> supportedDirections = EnumSet.of(Direction.UNDEFINED, Direction.LEFT, Direction.RIGHT);
        if (!hasEdges) {
            supportedDirections.add(Direction.UP);
            supportedDirections.add(Direction.DOWN);
        }

        // initializing fields
        cGraph = new CGraph(supportedDirections);

        // importing LGraphElements into CNodes
        readNodes();

        return cGraph;
    }

    /**
     * Collects the positions and dimensions of {@link LNode}s and vertical segments in the layered
     * graph and writes them to the {@link CNode}s.
     */
    private void readNodes() {
        List<VerticalSegment> verticalSegments = Lists.newArrayList();
        // resetting to avoid problems if this is called repeatedly
        cGraph.cNodes.clear();

        // 1. collecting positions of graph elements
        Map<LNode, CNode> nodeMap = Maps.newHashMap();
        for (Layer layer : layeredGraph) {
            for (LNode node : layer) {

                // comment boxes are part of a node's margins 
                //  hence we can neglect them here without the risk 
                // of other nodes overlapping them after compaction
                if (node.getProperty(LayeredOptions.COMMENT_BOX)) {
                    if (!Iterables.isEmpty(node.getConnectedEdges())) {
                        LEdge e = Iterables.get(node.getConnectedEdges(), 0);
                        LNode other = e.getSource().getNode();
                        if (other == node) {
                            other = e.getTarget().getNode();
                        }
                        Pair<LNode, KVector> p = Pair.of(other,
                                node.getPosition().clone().sub(other.getPosition()));
                        commentOffsets.put(node, p);
                        continue;
                    }
                }

                // add all nodes
                CLNode cNode = new CLNode(node, layeredGraph);
                cGraph.cNodes.add(cNode);
                nodeMap.put(node, cNode);
            }
        }

        for (Layer layer : layeredGraph) {
            for (LNode node : layer) {
                final CNode cNode = nodeMap.get(node);

                // add vertical edge segments
                for (LEdge edge : node.getOutgoingEdges()) {

                    Iterator<KVector> bends = edge.getBendPoints().iterator();

                    boolean first = true;
                    VerticalSegment lastSegment = null;
                    // infer vertical segments from positions of bendpoints
                    if (bends.hasNext()) {
                        KVector bend1 = bends.next();
                        KVector bend2 = null;

                        // get segment of source n/s port
                        if (edge.getSource().getSide() == PortSide.NORTH) {
                            VerticalSegment vs = new VerticalSegment(bend1, new KVector(bend1.x, cNode.hitbox.y),
                                    cNode, edge);
                            vs.blockBottomSpacing = true;
                            verticalSegments.add(vs);
                        }

                        if (edge.getSource().getSide() == PortSide.SOUTH) {
                            VerticalSegment vs = new VerticalSegment(bend1,
                                    new KVector(bend1.x, cNode.hitbox.y + cNode.hitbox.height), cNode, edge);
                            vs.blockTopSpacing = true;
                            verticalSegments.add(vs);
                        }

                        // get regular segments
                        while (bends.hasNext()) {
                            bend2 = bends.next();
                            if (!CompareFuzzy.eq(bend1.y, bend2.y)) {
                                lastSegment = new VerticalSegment(bend1, bend2, null, edge);
                                verticalSegments.add(lastSegment);

                                // the first vertical segment of an outgoing edge
                                if (first) {
                                    first = false;

                                    if (bend2.y < cNode.hitbox.y) {
                                        lastSegment.blockBottomSpacing = true;
                                    } else if (bend2.y > cNode.hitbox.y + cNode.hitbox.height) {
                                        lastSegment.blockTopSpacing = true;
                                    } else {
                                        // completely surrounded
                                        lastSegment.blockTopSpacing = true;
                                        lastSegment.blockBottomSpacing = true;
                                    }
                                }
                            }

                            if (bends.hasNext()) {
                                bend1 = bend2;
                            }
                        }

                        // handle last vertical segment
                        if (lastSegment != null) {
                            CNode cTargetNode = nodeMap.get(edge.getTarget().getNode());
                            if (bend1.y < cTargetNode.hitbox.y) {
                                lastSegment.blockBottomSpacing = true;
                            } else if (bend1.y > cTargetNode.hitbox.y + cTargetNode.hitbox.height) {
                                lastSegment.blockTopSpacing = true;
                            } else {
                                // completely surrounded
                                lastSegment.blockTopSpacing = true;
                                lastSegment.blockBottomSpacing = true;
                            }
                        }
                    }
                }

                // same for incoming edges to get NSSegments on target side
                for (LEdge edge : node.getIncomingEdges()) {
                    if (!edge.getBendPoints().isEmpty()) {

                        // get segment of target n/s port
                        KVector bend1 = edge.getBendPoints().getLast();
                        if (edge.getTarget().getSide() == PortSide.NORTH) {
                            VerticalSegment vs = new VerticalSegment(bend1, new KVector(bend1.x, cNode.hitbox.y),
                                    cNode, edge);
                            vs.blockBottomSpacing = true;
                            verticalSegments.add(vs);
                        }

                        if (edge.getTarget().getSide() == PortSide.SOUTH) {
                            VerticalSegment vs = new VerticalSegment(bend1,
                                    new KVector(bend1.x, cNode.hitbox.y + cNode.hitbox.height), cNode, edge);
                            vs.blockTopSpacing = true;
                            verticalSegments.add(vs);
                        }

                    }
                }
            }
        }

        // 2. combining intersecting segments in CLEdges to process them as one
        if (!verticalSegments.isEmpty()) {
            // sorting the segments by position in ascending order
            Collections.sort(verticalSegments);

            // merging intersecting segments in the same CLEdge
            VerticalSegment last = verticalSegments.get(0);
            CLEdge c = new CLEdge(last, layeredGraph);

            for (int i = 1; i < verticalSegments.size(); i++) {

                VerticalSegment verticalSegment = verticalSegments.get(i);

                if (c.intersects(verticalSegment)) {
                    c.addSegment(verticalSegment);
                } else {
                    cGraph.cNodes.add(c);
                    c = new CLEdge(verticalSegment, layeredGraph);
                }

                last = verticalSegment;
            }
            cGraph.cNodes.add(c);
        }

        verticalSegments.clear();

        // 3. grouping nodes with their connected north/south segments
        groupCNodes();
    }

    /**
     * Groups nodes with their connected north/south segments to keep them at the correct position
     * relative to each other.
     */
    private void groupCNodes() {
        // resetting groups from previous compaction
        cGraph.cGroups.clear();
        // necessary because of the exception in CGroup.addCNode
        for (CNode cNode : cGraph.cNodes) {
            cNode.cGroup = null;
        }

        // creating groups for independent CNodes
        for (CNode cNode : cGraph.cNodes) {
            if (cNode.parentNode == null) {
                cGraph.cGroups.add(new CGroup(cNode));
            }
        }

        // adding CNodes of north/south segments to the same group as their parent nodes
        for (CNode cNode : cGraph.cNodes) {
            if (cNode.parentNode != null) {
                cNode.parentNode.cGroup.addCNode(cNode);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void applyLayout() {
        // applying the compacted positions
        applyNodePositions();

        // adjust comment boxes
        applyCommentPositions();

        // calculating new graph size and offset
        KVector topLeft = new KVector(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
        KVector bottomRight = new KVector(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
        for (CNode cNode : cGraph.cNodes) {
            topLeft.x = Math.min(topLeft.x, cNode.hitbox.x);
            topLeft.y = Math.min(topLeft.y, cNode.hitbox.y);
            bottomRight.x = Math.max(bottomRight.x, cNode.hitbox.x + cNode.hitbox.width);
            bottomRight.y = Math.max(bottomRight.y, cNode.hitbox.y + cNode.hitbox.height);
        }
        layeredGraph.getOffset().reset().add(topLeft.clone().negate());
        layeredGraph.getSize().reset().add(bottomRight.clone().sub(topLeft));

        applyExternalPortPositions(topLeft, bottomRight);

        // resetting lists
        cGraph.cGroups.clear();
        cGraph.cNodes.clear();
    }

    /**
     * Applies the compacted positions to the
     * {@link de.cau.cs.kieler.klay.layered.graph.LGraphElement LGraphElement}s represented by
     * {@link CNode}s.
     */
    private void applyNodePositions() {
        for (CNode cNode : cGraph.cNodes) {
            cNode.applyElementPosition();
        }
    }

    private void applyCommentPositions() {
        for (Entry<LNode, Pair<LNode, KVector>> e : commentOffsets.entrySet()) {
            LNode comment = e.getKey();
            LNode other = e.getValue().getFirst();
            KVector offset = e.getValue().getSecond();
            comment.getPosition().reset().add(other.getPosition().clone().add(offset));
        }
    }

    private void applyExternalPortPositions(final KVector topLeft, final KVector bottomRight) {

        double borderSpacing = layeredGraph.getProperty(LayeredOptions.SPACING_BORDER).doubleValue();

        for (CNode cNode : cGraph.cNodes) {
            if (cNode instanceof CLNode) {
                LNode lNode = ((CLNode) cNode).getlNode();
                if (lNode.getType() == NodeType.EXTERNAL_PORT) {
                    switch (lNode.getProperty(InternalProperties.EXT_PORT_SIDE)) {
                    case WEST:
                        lNode.getPosition().x = topLeft.x - borderSpacing;
                        break;
                    case EAST:
                        lNode.getPosition().x = bottomRight.x + borderSpacing
                                - (lNode.getSize().x + lNode.getMargin().right);
                        break;
                    case NORTH:
                        lNode.getPosition().y = topLeft.y - borderSpacing;
                        break;
                    case SOUTH:
                        lNode.getPosition().y = bottomRight.y + borderSpacing
                                - (lNode.getSize().y + lNode.getMargin().bottom);
                        break;
                    }
                }
            }
        }
    }

}