org.eclipse.titanium.graph.gui.layouts.BaseHierarchicalLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.titanium.graph.gui.layouts.BaseHierarchicalLayout.java

Source

/******************************************************************************
 * Copyright (c) 2000-2017 Ericsson Telecom AB
 * 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
 ******************************************************************************/
package org.eclipse.titanium.graph.gui.layouts;

import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;

import org.apache.commons.collections15.Transformer;
import org.eclipse.titanium.Activator;
import org.eclipse.titanium.error.GUIErrorHandler;
import org.eclipse.titanium.graph.gui.layouts.algorithms.HierarcicalLayoutAlgorithm;
import org.eclipse.titanium.metrics.IMetricEnum;
import org.eclipse.titanium.preferences.PreferenceConstants;

import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;

/**
 * This is the base class of hierarchical layouts (reverse and normal)
 * 
 * @author Gabor Jenei
 * @param <V>
 *            The node type
 * @param <E>
 *            The edge type
 */
public abstract class BaseHierarchicalLayout<V, E> implements Layout<V, E> {
    private static final Double INNER_NODE_POSITION_RATIO_Y = 0.5;
    private static final Double INNER_NODE_POSITION_RATIO_X = 0.5;

    public static final String MAX_DISTANCE_ALGORITHM = "MAX";
    public static final String SUM_DISTANCE_ALGORITHM = "SUM";

    protected Graph<V, E> graph;
    protected HierarcicalLayoutAlgorithm<V> alg;
    protected Map<V, Point2D> places;
    protected Dimension size;
    protected final String distanceAlgorithm;
    protected IMetricEnum chosenMetric = null;
    protected final GUIErrorHandler errorHandler = new GUIErrorHandler();

    protected final Comparator<V> nodeComparator = new Comparator<V>() {
        @Override
        public int compare(final V v1, final V v2) {
            return Integer.signum(getNeighbours(v2).size() - getNeighbours(v1).size());
        }
    };

    /**
     * Constructor
     * 
     * @param g
     *            : The graph to lay out
     * @param size
     *            : the layout's size
     */
    public BaseHierarchicalLayout(final Graph<V, E> g, final Dimension size) {
        graph = g;
        places = new HashMap<V, Point2D>();
        this.size = size;
        distanceAlgorithm = Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.DAG_DISTANCE);
        initAlg();
        initialize();
    }

    public BaseHierarchicalLayout(final Graph<V, E> g, final Dimension size, final IMetricEnum metric) {
        chosenMetric = metric;
        graph = g;
        places = new HashMap<V, Point2D>();
        this.size = size;
        distanceAlgorithm = Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.DAG_DISTANCE);
        initAlg();
        initialize();
    }

    /**
     * This method implements the initialization of algorithm.
     * We should initialize the <code>protected</code> {@link #alg}
     * attribute here in subclasses
     */
    protected abstract void initAlg();

    /**
     * @param v
     *            : The node to to tell position for
     * @return Returns the position calculated for a given node
     */
    @Override
    public Point2D transform(final V v) {
        final Point2D p = places.get(v);

        return p != null ? p : new Point2D.Double();
    }

    /**
     * @return Returns the currently set graph, that is being laid out
     */
    @Override
    public Graph<V, E> getGraph() {
        return graph;
    }

    /**
     * @return Returns the currently set size for the drawing area (where the
     *         whole graph is shown, it is usually more than the screen size)
     */
    @Override
    public Dimension getSize() {
        return size;
    }

    /**
     * This method runs the actual algorithm, here happen the time consuming
     * calculations.
     */
    @Override
    public final void initialize() {
        alg.run();
        organizeNodes();
    }

    /**
     * As there is no locking option in this class this method returns constant
     * <code>false</code>
     * 
     * @param v
     *            : The node to check
     */
    @Override
    public boolean isLocked(final V v) {
        return false;
    }

    /**
     * This method is not used, therefore it does not make any change.
     * 
     * @param v
     *            : the node to set
     * @param lock
     *            : Should we lock, or unlock it?
     */
    @Override
    public void lock(final V v, final boolean lock) {
        // intentionally empty
    }

    /**
     * This method is not used, therefore it is empty (does not do anything)
     */
    @Override
    public void reset() {
        // intentionally empty
    }

    /**
     * This method changes the graph to lay out
     * 
     * @param g
     *            : the graph to set
     */
    @Override
    public void setGraph(final Graph<V, E> g) {
        graph = g;
    }

    /**
     * It is <b>discouraged</b> to use this method. It changes the transformer
     * used in the layout. After this change we cannot provide tree like view.
     * 
     * @param trf
     *            : The transformer to use
     */
    @Override
    public void setInitializer(final Transformer<V, Point2D> trf) {
        // intentionally empty
    }

    /**
     * Set one node's place on the canvas. This position maybe overwritten while
     * the algorithm is running (see {@link #initialize()})
     * 
     * @param v
     *            : The node to set position for
     * @param p
     *            : the claimed position
     */
    @Override
    public void setLocation(final V v, final Point2D p) {
        places.put(v, p);
    }

    /**
     * Set the draw area's (canvas) size. This attribute will determine the
     * width and height of the area used for drawing the whole graph.
     * 
     * @param size
     *            : the size to use
     */
    @Override
    public void setSize(final Dimension size) {
        this.size = size;
    }

    /**
     * Makes an X coordinate related ordering of the nodes. Tries to identify
     * the optimal X position for all nodes according to their distance to the
     * predecessor nodes.
     */
    @SuppressWarnings("unchecked")
    protected void organizeNodes() {
        final Map<V, Integer> nodeLevels = alg.getLevels();
        final int[] nodesPerLevel = alg.getNumberOfNodesPerLevel();
        final int noLevels = alg.getNumberOfLevels();
        final Set<V> isolateNodes = alg.getIsolateNodes();
        final double cellHeight = (double) size.height / noLevels;
        double[] cellWidths = new double[noLevels];
        Set<Integer>[] freePlaces = new HashSet[noLevels];
        final int baseLevel = isolateNodes.isEmpty() ? 0 : 1;
        Queue<V>[] levels = new PriorityQueue[noLevels];

        for (int i = 0; i < noLevels; ++i) {
            levels[i] = new PriorityQueue<V>(nodesPerLevel[i], nodeComparator);
        }

        // build an array that contains the nodes ordered separated by the
        // levels
        for (final Map.Entry<V, Integer> entry : nodeLevels.entrySet()) {
            levels[entry.getValue()].add(entry.getKey());
        }

        // set all cells free inside the rows
        for (int i = 0; i < noLevels; i++) {
            cellWidths[i] = (double) size.width / nodesPerLevel[i];
            freePlaces[i] = new HashSet<Integer>();
            for (int actCell = 0; actCell < nodesPerLevel[i]; ++actCell) {
                freePlaces[i].add(actCell);
            }
        }

        // place first isolate nodes (if there is any)
        int noPlacedElems = 0;
        for (final V v : isolateNodes) {
            final double actHeight = cellHeight * INNER_NODE_POSITION_RATIO_Y;
            final double actXPos = cellWidths[0] * ((noPlacedElems++) + INNER_NODE_POSITION_RATIO_X);
            final Point2D p = new Point2D.Double(actXPos, actHeight);
            places.put(v, p);
        }

        if (baseLevel >= noLevels && noPlacedElems != 0) {
            return;
        }

        // place the initial first row's nodes
        noPlacedElems = 0;
        for (final V v : levels[baseLevel]) {
            final double actHeight = cellHeight * (baseLevel + INNER_NODE_POSITION_RATIO_Y);
            final double actXPos = cellWidths[baseLevel] * ((noPlacedElems++) + INNER_NODE_POSITION_RATIO_X);
            places.put(v, new Point2D.Double(actXPos, actHeight));
        }

        boolean badDistance = false;
        if (!distanceAlgorithm.equals(MAX_DISTANCE_ALGORITHM)
                && !distanceAlgorithm.equals(SUM_DISTANCE_ALGORITHM)) {
            errorHandler.reportBadSetting("Distance algorithm error",
                    "Not existing distance algorithm is set, for details see the preference page"
                            + "\n (DistanceAlgorithm=" + distanceAlgorithm + ")",
                    "Open preference page", "org.eclipse.titanium.preferences.pages.GraphPreferencePage");
            badDistance = true;
        }

        // set optimal place for inner rows
        for (int actLevel = baseLevel + 1; actLevel < noLevels; ++actLevel) {
            final double actHeight = cellHeight * (actLevel + INNER_NODE_POSITION_RATIO_Y);
            noPlacedElems = 0;
            for (final V v : levels[actLevel]) {
                if (!badDistance) {
                    places.put(v, new Point2D.Double(
                            getBestXPosition(v, freePlaces[actLevel], cellWidths[actLevel]), actHeight));
                } else {
                    places.put(v, new Point2D.Double(
                            cellWidths[actLevel] * ((noPlacedElems++) + INNER_NODE_POSITION_RATIO_X), actHeight));
                }
            }
        }
    }

    /**
     * Calculates the optimal X position for a given node
     * 
     * @param v
     *            : The node to calculate the optimal position for
     * @param freePlaces
     *            : A <code>Set</code> containing the free places inside the
     *            node's row
     * @param cellWidth
     *            : The calculated cellWidth inside the node's row
     * @return The optimal X coordinate
     * @throws Exception
     */
    protected double getBestXPosition(final V v, final Set<Integer> freePlaces, final double cellWidth) {
        double position = 0;

        double actDistance = 0;
        double minDistance = Double.POSITIVE_INFINITY;
        Integer givenCell = Integer.MAX_VALUE;
        final Collection<V> neighbours = getNeighbours(v);

        for (final int actCell : freePlaces) {
            final double actPos = cellWidth * (actCell + INNER_NODE_POSITION_RATIO_X);
            if (distanceAlgorithm.equals(SUM_DISTANCE_ALGORITHM)) {
                actDistance = getSumDistance(actPos, neighbours);
            } else if (distanceAlgorithm.equals(MAX_DISTANCE_ALGORITHM)) {
                actDistance = getMaxDistance(actPos, neighbours);
            }

            if (minDistance > actDistance) {
                minDistance = actDistance;
                givenCell = actCell;
                position = actPos;
            }
        }

        freePlaces.remove(givenCell);
        return position;
    }

    /**
     * This method is used to return the nodes belonging to the next level
     * of DAG layout, these can be the predecessors or oppositely the
     * successors depending on the used algorithm
     * 
     * @param v : The source node
     * @return The nodes that should be placed on the next level compared to v
     */
    protected abstract Collection<V> getNeighbours(V v);

    /**
     * Implementation of sum distance. This distance is defined as the sum of
     * individual distances to the predecessor nodes
     * 
     * @param pos
     *            : The suspected position of the node
     * @param neighbours
     *            : The list of used neighbour nodes (predecessors, or
     *            successors)
     * @return The distance
     */
    protected double getSumDistance(final double pos, final Collection<V> neighbours) {
        double distance = 0;

        for (final V node : neighbours) {
            final Point2D pn = places.get(node);
            if (pn == null) {
                continue;
            }
            distance += Math.abs(pn.getX() - pos);
        }
        return distance;
    }

    /**
     * Implementation of max distance This distance is defined as the maximum of
     * the distances to the predecessor nodes
     * 
     * @param pos
     *            : The suspected position of the node
     * @param neighbours
     *            : The list of used neighbour nodes (predecessors, or
     *            successors)
     * @return The distance
     */
    protected double getMaxDistance(final double pos, final Collection<V> neighbours) {
        double distance = Double.NEGATIVE_INFINITY;

        for (final V v : neighbours) {
            final Point2D pv = places.get(v);
            if (pv == null) {
                continue;
            }

            final double actDist = Math.abs(pv.getX() - pos);
            if (actDist > distance) {
                distance = actDist;
            }
        }
        return distance;
    }

}