/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Graph.java
* Creation date: (02/15/02 12:47:22 PM)
* By: Edward Lam
*/
package org.openquark.gems.client;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* A class used to represent a directed Graph.
* For now this is used to enable graph rearrangement.
*
* Creation date: (02/15/02 12:47:22 PM)
* @author Edward Lam
*/
class Graph {
/**
* The interface for all generic node type information
* Creation date: (11/7/01 9:37:37 AM)
* @author Luke Evans
*/
interface Node {
/**
* Set the location of this Node
* Creation date: (12/5/00 10:21:00 PM)
* @param newXY Point the new location
*/
public void setLocation(Point newXY);
/**
* Get the location of this Gem
* Creation date: (12/5/00 10:21:00 PM)
* @return newXY Point the location
*/
public Point getLocation();
/**
* Get the degree of this graph node.
* Creation date: (11/8/01 3:45:00 PM)
* @return double
*/
public double degree ();
/**
* Get an iterator over the incoming edges.
* Creation date: (11/8/01 3:45:00 PM)
* @param validNodes the nodes to be considered
* @return Iterator
*/
public Iterator<Edge> getInEdges (List<Node> validNodes);
/**
* Get an iterator over the outgoing edges.
* Creation date: (11/8/01 3:45:00 PM)
* @param validNodes the nodes to be considered
* @return Iterator
*/
public Iterator<Edge> getOutEdges (List<Node> validNodes);
/**
* Get the bounds of the node.
*/
public java.awt.Rectangle getBounds();
public String getDisplayText ();
}
/**
* The interface for all generic edge type information
* Creation date: (11/8/01 9:37:37 AM)
* @author Raymond Cypher
*/
interface Edge {
/**
* Get the source node of this edge.
* Creation date: (11/8/01 9:37:37 AM)
* @return Node
*/
public Node getSourceNode ();
/**
* Get the destination node of this edge.
* Creation date: (11/8/01 9:37:37 AM)
* @return Node
*/
public Node getTargetNode ();
/**
* Get the x/y coordinates of the point where this
* edge connects to the target.
* Creation date: (11/8/01 9:37:37 AM)
* @return Node
*/
public Point getTargetConnectionPoint ();
/**
* Get the x/y coordinates of the point where this
* edge connects to the source.
* Creation date: (11/8/01 9:37:37 AM)
* @return Node
*/
public Point getSourceConnectionPoint ();
}
/**
* LayoutArranger attempts to tidy the GemGraph by 'recrystalising it'.
* @author Raymond Cypher
*/
public static class LayoutArranger {
// all the nodes to be arranged
private final Node [] nodes;
// The 'gaps' between each gem.
private static final int hGap = DisplayConstants.HALO_BLUR_SIZE * 4;
private static final int vGap = DisplayConstants.HALO_BLUR_SIZE * 2;
// The roots and trees stored in layout arranger.
private final List<Node> roots = new ArrayList<Node>();
/** the list of nodes that the trees contain */
private final List<Node> validNodes;
/**
* Default Constructor for LayoutArranger
* @param nodes the list of validNodes
*/
public LayoutArranger (Node[] nodes) {
this.validNodes = Arrays.asList(nodes);
this.nodes = nodes.clone();
}
/**
* This function sorts the tree objects in the given list in order of their Y
* coordinates. So the tree with the lowest Y coordinate is placed first
* @param trees the trees to be sorted
*/
static void sortTreesOnYOrder(List<Tree> trees) {
Comparator<Tree> treeComparator = new Comparator<Tree>() {
/**
* @see java.util.Comparator#compare(Object, Object)
*/
public int compare(Tree tree1, Tree tree2) {
Rectangle bounds1 = tree1.getBounds();
Rectangle bounds2 = tree2.getBounds();
return bounds1.y - bounds2.y;
}
};
Collections.sort(trees, treeComparator);
}
/**
* This function finds all the roots in the validNode Set
*/
private void findRoots() {
for (int i = 0; i < nodes.length; ++i) {
if (!nodes [i].getOutEdges(validNodes).hasNext()) {
roots.add (nodes [i]);
}
}
}
/**
* This function rearranges the graph so that it will be 'neater'
*/
void arrangeGraph () {
findRoots();
try {
// Builds the trees, and them in one function
createAndRearrangeTrees();
} catch (Exception e) {
System.out.println ("Exception arranging trees: " + e);
}
}
/**
* Creates the tree and add them to the vector
*/
private void createAndRearrangeTrees () {
for (int i = 0; i < roots.size (); ++i) {
Node gn = roots.get (i);
new Tree (gn, true); // The constructor arranges the tree..
}
}
/**
* Creates the tree and add them to the vector
* @return List
*/
List<Tree> getTrees () {
List<Tree> existingTrees = new ArrayList<Tree>();
findRoots();
for (int i = 0; i < roots.size (); ++i) {
Node gn = roots.get (i);
existingTrees.add(new Tree(gn, false));
}
return existingTrees;
}
/**
* A tree class that stores the structure of a tree. Used in the LayoutArranger for rearranging trees.]
* Note: data is stored recursively. Each tree is stored with reference to subtrees.
* @author ?
* Date Created: ?
*/
class Tree {
// To store more information regarding the tree.
private final List<Tree> subTrees = new ArrayList<Tree> ();
private final Node root;
/** The complete bounds of this tree */
private Rectangle bounds = null;
/**
* Default constructor for the tree structure
* @param root
* @param rearrangeSubTree
*/
Tree (Node root, boolean rearrangeSubTree) {
this.root = root;
for (Iterator<Edge> ins = root.getInEdges(validNodes); ins.hasNext(); ) {
Edge gc = ins.next ();
Node sub = gc.getSourceNode ();
subTrees.add (new Tree(sub, rearrangeSubTree));
}
if (rearrangeSubTree) {
arrange ();
}
}
/**
* returns all the nodes in this tree
* @param nodes (the set that the nodes will be stored in)
*/
void getNodes(Set<Node> nodes) {
for (int i = 0, size = subTrees.size(); i < size; i++) {
Tree current = subTrees.get(i);
current.getNodes(nodes);
}
nodes.add(root);
}
/**
* @return the root of the tree.
*/
public Node getRoot() {
return root;
}
/**
* get the width of the trees
* @return int
*/
int getWidth () {
return getBounds().width;
}
/**
* get the height of the trees
* @return int
*/
int getHeight () {
return getBounds().height;
}
/**
* get the bounds of the trees
* @return Rectangle
*/
Rectangle getBounds () {
bounds = root.getBounds();
for (int i = 0; i < subTrees.size(); i ++) {
bounds.add(subTrees.get(i).getBounds());
}
return bounds;
}
/**
* Rearranges the tree
*/
void arrange () {
bounds = arrangeChildren ();
}
/**
* Sets position of the root of the tree, and moves all its nodes respectively
* @param x the x coordinate of the root node
* @param y the y coordinate fo the root node
*/
void setLocation (int x, int y) {
// if the bounds have not been defined yet, then arrange the trees first
if (bounds == null) {
getBounds();
}
int dx = x - bounds.x;
int dy = y - bounds.y;
move (dx, dy);
}
/**
* Aligns the root of the tree with its children.
* @param subTree the subtree to be aligned
*/
void lineUpWithTarget (Tree subTree) {
Node node = subTree.root;
Iterator<Edge> oe = node.getOutEdges(validNodes);
if (!oe.hasNext()) {
return;
}
//System.out.println ("lineUpWithTarget (" + node.getDisplayText () + ")");
Edge oe1 = oe.next();
//System.out.println (" sn bounds = " + node.getBounds () + ", tn bounds = " + target.getBounds ());
//System.out.println (" src conn pt = " + oe1.getSourceConnectionPoint () + " - target conn pt = " + oe1.getTargetConnectionPoint ());
int vc1 = oe1.getSourceConnectionPoint ().y;
int vc2 = oe1.getTargetConnectionPoint ().y;
int deltaY = vc2 - vc1;
int hc1 = oe1.getSourceConnectionPoint ().x;
int hc2 = oe1.getTargetConnectionPoint ().x - hGap;
int deltaX = hc2 - hc1;
subTree.move (deltaX, deltaY);
}
/**
* Move the tree by the given deltas.
* Creation date: (11/23/01 1:50:16 PM)
* @param dx int
* @param dy int
*/
void move (int dx, int dy) {
Point p = root.getLocation ();
p.x += dx;
p.y += dy;
root.setLocation (p);
bounds.x += dx;
bounds.y += dy;
Iterator<Tree> i = subTrees.iterator ();
while (i.hasNext ()) {
Tree subTree = i.next ();
subTree.move (dx, dy);
}
}
/**
* Arranges the children of the given node and returns the bounds of the
* tree represented by the node.
* @return Rectangle
*/
Rectangle arrangeChildren () {
int nSubTrees = subTrees.size ();
if (nSubTrees == 0) {
return root.getBounds ();
}
int indent = -7;
int nIndents;
if ((nSubTrees % 2) == 0) {
nIndents = (nSubTrees - 2) / 2;
} else {
nIndents = (nSubTrees - 1) / 2;
}
int upperLoopStart = -1;
int lowerLoopStart = -1;
double ceiling = -1;
double floor = -1;
int middleIndex = ((int)(nSubTrees / 2f + 0.5f)) - 1;
Tree middleSub = subTrees.get (middleIndex);
lineUpWithTarget (middleSub);
double middleSubBottom = middleSub.getBounds ().getMaxY();
if ((nSubTrees % 2) == 0) {
// Arrange the two middle nodes.
Tree middleSub2 = subTrees.get (middleIndex + 1);
lineUpWithTarget (middleSub2);
double middleSub2Top = middleSub2.getBounds ().getMinY();
// Do they overlap?
if (middleSub2Top < (middleSubBottom + vGap)) {
double diff = ((middleSubBottom + vGap) - middleSub2Top) /2;
middleSub.move (nIndents * indent, -1 * (int)diff);
middleSub2.move (nIndents * indent, (int)diff);
} else {
middleSub.move (nIndents * indent, 0);
middleSub2.move (nIndents * indent, 0);
}
// If the two subtrees are of different depth try to snug them up.
int msd = middleSub.getBounds ().x;
int ms2d = middleSub2.getBounds ().x;
int gap = 0;
if (msd > ms2d) {
gap = middleSub2.getUpperBoundAt (msd) - (int)(middleSub.getBounds ().getMaxY ());
} else {
gap = middleSub2.getBounds ().y - middleSub.getLowerBoundAt (ms2d);
}
if (gap > vGap) {
gap -= vGap;
middleSub.move (0, gap / 2);
middleSub2.move (0, -1 * (gap / 2));
}
floor = middleSub2.getBounds ().getMaxY () + vGap;
ceiling = middleSub.getBounds ().getMinY () - vGap;
upperLoopStart = middleIndex - 1;
lowerLoopStart = middleIndex + 2;
} else {
middleSub.move (nIndents * indent, 0);
floor = middleSub.getBounds ().getMaxY () + vGap;
ceiling = middleSub.getBounds ().getMinY () - vGap;
upperLoopStart = middleIndex - 1;
lowerLoopStart = middleIndex + 1;
}
// Now do the upper loop. i.e. move up through the child connections
// starting from the middle.
int indentM = nIndents - 1;
for (int i = upperLoopStart; i >= 0; --i) {
Tree subTree = subTrees.get (i);
lineUpWithTarget (subTree);
int dx = indentM * indent;
indentM --;
int dy = 0;
if (subTree.getBounds ().getMaxY () > ceiling) {
dy = (int)(ceiling - subTree.getBounds ().getMaxY ());
}
subTree.move (dx, dy);
if (i + 1 < subTrees.size ()) {
int subTreeBottom = subTree.getBounds ().y + subTree.getBounds ().height;
int subTreeLeft = subTree.getBounds ().x;
int highestAtLeft = Integer.MAX_VALUE;
for (int j = i + 1; j <= upperLoopStart + 1; ++j) {
Tree prevTree = subTrees.get (j);
int ub = prevTree.getUpperBoundAt (subTreeLeft);
if (ub < highestAtLeft) {
highestAtLeft = ub;
}
}
int gap = highestAtLeft - subTreeBottom;
if (gap > vGap) {
gap -= vGap;
subTree.move (0, gap);
}
}
ceiling = subTree.getBounds ().getMinY () - vGap;
}
// Now do the lower loop.
indentM = nIndents - 1;
for (int i = lowerLoopStart; i < subTrees.size (); ++i) {
Tree subTree = subTrees.get (i);
lineUpWithTarget (subTree);
int dx = indentM * indent;
indentM --;
int dy = 0;
if (subTree.getBounds ().getMinY () < floor) {
dy = (int)(floor - subTree.getBounds ().getMinY ());
}
subTree.move (dx, dy);
if (i > 1) {
int subTreeTop = subTree.getBounds ().y;
int subTreeLeft = subTree.getBounds ().x;
int lowestAtLeft = Integer.MIN_VALUE;
for (int j = i - 1; j >= lowerLoopStart - 1; --j) {
Tree prevTree = subTrees.get (j);
int lb = prevTree.getLowerBoundAt (subTreeLeft);
if (lb > lowestAtLeft) {
lowestAtLeft = lb;
}
}
int gap = subTreeTop - lowestAtLeft;
if (gap > vGap) {
gap -= vGap;
subTree.move (0, -1 * gap);
}
}
floor = subTree.getBounds ().getMaxY () + vGap;
}
int top = Integer.MAX_VALUE;
int bottom = Integer.MIN_VALUE;
int left = Integer.MAX_VALUE;
for (int i = 0; i < subTrees.size (); ++i) {
Tree t = subTrees.get (i);
Rectangle b = t.getBounds ();
if (b.x < left) {
left = b.x;
}
if (b.y < top) {
top = b.y;
}
if (b.y + b.height > bottom) {
bottom = (b.y + b.height);
}
}
int right = (int)root.getBounds().getMaxX ();
if (top > root.getBounds ().y) {
top = root.getBounds ().y;
}
if (bottom < (int)root.getBounds().getMaxY ()) {
bottom = (int)root.getBounds().getMaxY ();
}
return new Rectangle (left, top, right - left, bottom - top);
}
/**
* Determines the upper bound of the rectangle bounding
* the subtree out to the the given x-coordinate.
* @param x int
*/
int getUpperBoundAt (int x) {
Rectangle b = root.getBounds ();
if (b.x < x || subTrees.size () == 0) {
return b.y;
}
Tree subTree = subTrees.get (0);
int upper = subTree.getUpperBoundAt (x);
if (upper > b.y) {
return b.y;
}
return upper;
}
/**
* Determines the lower bound of the rectangle bounding
* the subtree out to the the given x-coordinate.
* @param x int
*/
int getLowerBoundAt (int x) {
Rectangle b = root.getBounds ();
if (b.x < x || subTrees.size () == 0) {
return (b.y + b.height);
}
Tree subTree = subTrees.get (subTrees.size () - 1);
int lower = subTree.getLowerBoundAt (x);
if (lower < (b.y + b.height)) {
return (b.y + b.height);
}
return lower;
}
}
}
}