org.eclipse.elk.tree.p3place.NodePlacer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.tree.p3place.NodePlacer.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2015 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.tree.p3place;

import java.util.EnumSet;
import java.util.LinkedList;

import org.eclipse.elk.core.util.IElkProgressMonitor;
import org.eclipse.elk.tree.ILayoutPhase;
import org.eclipse.elk.tree.IntermediateProcessingConfiguration;
import org.eclipse.elk.tree.TreeUtil;
import org.eclipse.elk.tree.graph.TGraph;
import org.eclipse.elk.tree.graph.TNode;
import org.eclipse.elk.tree.intermediate.IntermediateProcessorStrategy;
import org.eclipse.elk.tree.properties.Properties;

import com.google.common.collect.Iterables;

/**
 * The algorithm comes from
 * <ul>
 *   <li> John Q.Walker II, A Node-Positioning Algorithm for General Trees,
 *     <em>Software: Practice and Experience</em> 20(7), pp. 685-705, July 1990.</li>
 * </ul>
 * with some small fixes in the actual code.
 * 
 * <p>
 * This algorithm utilizes two concepts developed in previous positioning algorithms. First is the
 * concept of building subtrees as rigid units. When a node is moved, all of its descendants (if it
 * has any) are also moved--the entire subtree being thus treated as a rigid unit. A general tree is
 * positioned by building it up recursively from its leaves toward its root.
 * </p>
 * 
 * <p>
 * Second is the concept of using two fields for the positioning of each node. These two fields are:
 * <ul>
 *   <li> a preliminary x-coordinate
 *   <li> a modifier field.
 * </ul>
 * Two tree traversals are used to produce the final x-coordinate of a node. The first traversal
 * assigns the preliminary x-coordinate and modifier fields for each node; the second traversal
 * computes the final x-coordinate of each node by summing the node's preliminary x-coordinate with
 * the modifier fields of all of its ancestors. The final y-coordinate of the node is the height of
 * the node's ancestors levels and the height nodes's level and the adjust of the root location.
 * 
 * @author sor
 * @author sgu
 */
public class NodePlacer implements ILayoutPhase {

    private double spacing;

    /** Determine how to adjust all the nodes with respect to the location of the root. */
    private double xTopAdjustment = 0d;
    private double yTopAdjustment = 0d;

    /**
     * {@inheritDoc}
     */
    public IntermediateProcessingConfiguration getIntermediateProcessingConfiguration(final TGraph tGraph) {
        return INTERMEDIATE_PROCESSING_CONFIGURATION;
    }

    /**
     * intermediate processing configuration. The neighbors processor needs to run again right
     * before the phase to get the actual order
     */
    private static final IntermediateProcessingConfiguration INTERMEDIATE_PROCESSING_CONFIGURATION = new IntermediateProcessingConfiguration(
            null, EnumSet.of(IntermediateProcessorStrategy.ROOT_PROC),
            EnumSet.of(IntermediateProcessorStrategy.LEVEL_HEIGHT, IntermediateProcessorStrategy.NEIGHBORS_PROC),
            EnumSet.of(IntermediateProcessorStrategy.NODE_POSITION_PROC));

    /**
     * {@inheritDoc}
     */
    public void process(final TGraph tGraph, final IElkProgressMonitor progressMonitor) {
        progressMonitor.begin("Processor order nodes", 2);

        /** set the spacing according to the user inputs */
        spacing = tGraph.getProperty(Properties.SPACING).doubleValue();

        /** find the root node of this component */
        LinkedList<TNode> roots = new LinkedList<TNode>();
        for (TNode tNode : tGraph.getNodes()) {
            if (tNode.getProperty(Properties.ROOT)) {
                roots.add(tNode);
            }
        }
        TNode root = roots.getFirst();

        /** Do the preliminary positioning with a postorder walk. */
        firstWalk(root, 0);
        progressMonitor.worked(1);

        /** Do the final positioning with a preorder walk. */
        secondWalk(root, root.getProperty(Properties.LEVELHEIGHT) + yTopAdjustment, xTopAdjustment);
        progressMonitor.worked(1);

        progressMonitor.done();
    }

    /**
     * In this first postorder walk, every node of the tree is assigned a preliminary x-coordinate
     * (held in property PRELIM). In addition, internal nodes are given modifiers, which will be
     * used to move their offspring to the right (held in property MODIFIER).
     * 
     * @param cN
     *            the root level of the tree
     * @param level
     *            the index of the passed level
     */
    private void firstWalk(final TNode cN, final int level) {
        cN.setProperty(Properties.MODIFIER, 0d);
        TNode lS = cN.getProperty(Properties.LEFTSIBLING);

        if (cN.isLeaf()) {
            if (lS != null) {
                /**
                 * Determine the preliminary x-coordinate based on: the preliminary x-coordinate of
                 * the left sibling, the separation between sibling nodes, and tHe mean size of left
                 * sibling and current node.
                 */
                double p = lS.getProperty(Properties.PRELIM) + spacing + meanNodeWidth(lS, cN);
                cN.setProperty(Properties.PRELIM, p);
            } else {
                /** No sibling on the left to worry about. */
                cN.setProperty(Properties.PRELIM, 0d);
            }
        } else {
            /**
             * This Node is not a leaf, so call this procedure recursively for each of its
             * offspring.
             */
            for (TNode child : cN.getChildren()) {
                firstWalk(child, level + 1);
            }

            /**
             * Set the prelim and modifer for this node by determine the midpoint of its offsprings
             * and the middle node size of the node and its left sibling
             */
            TNode lM = Iterables.getFirst(cN.getChildren(), null);
            TNode rM = Iterables.getLast(cN.getChildren(), null);
            double midPoint = (rM.getProperty(Properties.PRELIM) + lM.getProperty(Properties.PRELIM)) / 2f;

            if (lS != null) {
                /** This Node has a left sibling so its offsprings must be shifted to the right */
                double p = lS.getProperty(Properties.PRELIM) + spacing + meanNodeWidth(lS, cN);
                cN.setProperty(Properties.PRELIM, p);
                cN.setProperty(Properties.MODIFIER, cN.getProperty(Properties.PRELIM) - midPoint);
                /** shift the offsprings of this node to the right */
                apportion(cN, level);
            } else {
                /** No sibling on the left to worry about. */
                cN.setProperty(Properties.PRELIM, midPoint);
            }
        }
    }

    /**
     * This method cleans up the positioning of small sibling subtrees, thus fixing the
     * "left-to-right gluing" problem evident in earlier algorithms. When moving a new subtree
     * farther and farther to the right, gaps may open up among smaller subtrees that were
     * previously sandwiched between larger subtrees. Thus, when moving the new, larger subtree to
     * the right, the distance it is moved is also apportioned to smaller, interior subtrees,
     * creating a pleasing aesthetic placement.
     * 
     * @param cN
     *            the root of the subtree
     * @param level
     *            the level of the root in the global tree
     */
    private void apportion(final TNode cN, final int level) {
        /** Initialize the leftmost and neighbor corresponding to the root of the subtree */
        TNode leftmost = Iterables.getFirst(cN.getChildren(), null);
        TNode neighbor = leftmost != null ? leftmost.getProperty(Properties.LEFTNEIGHBOR) : null;
        int compareDepth = 1;
        /**
         * until this node and the neighbor to the left have nodes in the current level we have to
         * shift the current subtree
         */
        while (leftmost != null && neighbor != null) {
            /** Compute the location of Leftmost and where it should be with respect to Neighbor. */
            double leftModSum = 0;
            double rightModSum = 0;
            TNode ancestorLeftmost = leftmost;
            TNode ancestorNeighbor = neighbor;
            /** sum the modifiers of all ancestors according to the current level */
            for (int i = 0; i < compareDepth; i++) {
                ancestorLeftmost = ancestorLeftmost.getParent();
                ancestorNeighbor = ancestorNeighbor.getParent();
                rightModSum += ancestorLeftmost.getProperty(Properties.MODIFIER);
                leftModSum += ancestorNeighbor.getProperty(Properties.MODIFIER);
            }
            /**
             * Find the MoveDistance, and apply it to Node's subtree. Add appropriate portions to
             * smaller interior subtrees.
             */
            double prN = neighbor.getProperty(Properties.PRELIM);
            double prL = leftmost.getProperty(Properties.PRELIM);
            double mean = meanNodeWidth(leftmost, neighbor);
            double moveDistance = prN + leftModSum + spacing + mean - prL - rightModSum;

            if (0 < moveDistance) {
                /** Count interior sibling subtrees in LeftSiblings */
                TNode leftSibling = cN;
                int leftSiblings = 0;
                while (leftSibling != null && leftSibling != ancestorNeighbor) {
                    leftSiblings++;
                    leftSibling = leftSibling.getProperty(Properties.LEFTSIBLING);
                }
                /** Apply portions to appropriate left sibling subtrees. */
                if (leftSibling != null) {
                    double portion = moveDistance / (double) leftSiblings;
                    leftSibling = cN;
                    while (leftSibling != ancestorNeighbor) {
                        double newPr = leftSibling.getProperty(Properties.PRELIM) + moveDistance;
                        leftSibling.setProperty(Properties.PRELIM, newPr);
                        double newMod = leftSibling.getProperty(Properties.MODIFIER) + moveDistance;
                        leftSibling.setProperty(Properties.MODIFIER, newMod);
                        moveDistance -= portion;
                        leftSibling = leftSibling.getProperty(Properties.LEFTSIBLING);
                    }
                } else {
                    /**
                     * Don't need to move anything--it needs to be done by an ancestor because
                     * AncestorNeighbor and AncestorLeftmost are not siblings of each other.
                     */
                    return;
                }
            }
            /**
             * Determine the leftmost descendant of Node at the next lower level to compare its
             * positioning against that of its Neighbor.
             */
            compareDepth++;
            if (leftmost.isLeaf()) {
                leftmost = TreeUtil.getLeftMost(cN.getChildren(), compareDepth);
            } else {
                leftmost = Iterables.getFirst(leftmost.getChildren(), null);
            }
            neighbor = leftmost != null ? leftmost.getProperty(Properties.LEFTNEIGHBOR) : null;
        }
    }

    /**
     * This function returns the mean width of the two passed nodes. It adds the width of the right
     * half of left hand node to the left half of right hand node. If all nodes are the same width,
     * this is a trivial calculation.
     * 
     * @param leftNode
     *            the left hand node
     * @param rightNode
     *            the right hand node
     * @return the sum of the width
     */
    private double meanNodeWidth(final TNode leftNode, final TNode rightNode) {
        double nodeWidth = 0d;
        if (leftNode != null) {
            nodeWidth += leftNode.getSize().x / 2d;
        }
        if (rightNode != null) {
            nodeWidth += rightNode.getSize().x / 2d;
        }
        return nodeWidth;
    }

    /**
     * During a second preorder walk, each node is given a final x-coordinate by summing its
     * preliminary x-coordinate and the modifiers of all the node's ancestors. The y-coordinate
     * depends on the height of the node's ancestors levels. If the actual position of an interior
     * node is right of its preliminary place, the subtree rooted at the node must be moved right to
     * center the sons around the father. Rather than immediately readjust all the nodes in the
     * subtree, each node remembers the distance to the provisional place in a modifier field
     * (property MODIFIER). In this second pass down the tree, modifiers are accumulated and applied
     * to every node.
     * 
     * @param tNode
     *            the root of the tree
     * @param yCoor
     *            the y coordinate of previous level
     * @param modsum
     *            the modifiers of all the node's ancestors
     */
    private void secondWalk(final TNode tNode, final double yCoor, final double modsum) {
        if (tNode != null) {
            // The x-position of the node is the sum of its prev x-coordinate and the modifiers of
            // all the node's ancestors and the adjust of the root location.
            double xTemp = tNode.getProperty(Properties.PRELIM) + modsum;
            // The y-position of the node is the height of the node's ancestors levels and the
            // height nodes's level and the adjust of the root location.
            double yTemp = yCoor + spacing + tNode.getProperty(Properties.LEVELHEIGHT);
            // We do not check to see that xTemp and yTemp are of the proper size, because the
            // framework will take care of this.
            tNode.setProperty(Properties.XCOOR, (int) Math.round(xTemp));
            tNode.setProperty(Properties.YCOOR, (int) Math.round(yTemp));
            // Apply the modifier value for this node to all its offspring.
            if (!tNode.isLeaf()) {
                // This node got offsprings so we will step a level down and take care of them.
                secondWalk(Iterables.getFirst(tNode.getChildren(), null), yTemp,
                        modsum + tNode.getProperty(Properties.MODIFIER));
            }
            // Go ahead with the sibling to the right. This is a dfs so we just layout the current
            // subtree.
            if (tNode.getProperty(Properties.RIGHTSIBLING) != null) {
                secondWalk(tNode.getProperty(Properties.RIGHTSIBLING), yCoor, modsum);
            }
        }
    }

}