blob: 7e38f59019502aed2518e6c387dd78f066803169 [file] [log] [blame]
/*******************************************************************************
* Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
* 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: The Chisel Group, University of Victoria
*******************************************************************************/
package org.eclipse.zest.layouts.algorithms;
import java.util.Iterator;
import java.util.List;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentPoint;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentRectangle;
import org.eclipse.zest.layouts.dataStructures.InternalNode;
import org.eclipse.zest.layouts.dataStructures.InternalRelationship;
/**
* This layout will take the given entities, apply a tree layout to them, and then display the
* tree in a circular fashion with the roots in the center.
*
* @author Casey Best
* @auhtor Rob Lintern
*/
public class RadialLayoutAlgorithm extends TreeLayoutAlgorithm {
private static final double MAX_DEGREES = Math.PI * 2;
private double startDegree;
private double endDegree;
private TreeLayoutAlgorithm treeLayout;
private List roots;
/**
* Creates a radial layout with no style.
*/
public RadialLayoutAlgorithm() {
this(LayoutStyles.NONE);
}
//TODO: This is a really strange pattern. It extends tree layout and it contains a tree layout ?
public RadialLayoutAlgorithm(int styles) {
super(styles);
treeLayout = new TreeLayoutAlgorithm(styles);
startDegree = 0;
endDegree = MAX_DEGREES;
}
public void setLayoutArea(double x, double y, double width, double height) {
throw new RuntimeException("Operation not implemented");
}
protected boolean isValidConfiguration(boolean asynchronous, boolean continueous) {
if (asynchronous && continueous)
return false;
else if (asynchronous && !continueous)
return true;
else if (!asynchronous && continueous)
return false;
else if (!asynchronous && !continueous)
return true;
return false;
}
DisplayIndependentRectangle layoutBounds = null;
protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
// TODO Auto-generated method stub
layoutBounds = new DisplayIndependentRectangle(x, y, width, height);
super.preLayoutAlgorithm(entitiesToLayout, relationshipsToConsider, x, y, width, height);
}
protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) {
roots = treeLayout.getRoots();
computeRadialPositions(entitiesToLayout, layoutBounds);
defaultFitWithinBounds(entitiesToLayout, layoutBounds);
super.postLayoutAlgorithm(entitiesToLayout, relationshipsToConsider);
}
/**
* Set the range the radial layout will use when applyLayout is called.
* Both values must be in radians.
*/
public void setRangeToLayout(double startDegree, double endDegree) {
this.startDegree = startDegree;
this.endDegree = endDegree;
}
/**
* Take the tree and make it round. This is done by determining the location of each entity in terms
* of its percentage in the tree layout. Then apply that percentage to the radius and distance from
* the center.
*/
protected void computeRadialPositions(InternalNode[] entities, DisplayIndependentRectangle bounds2) { //TODO TODO TODO
DisplayIndependentRectangle bounds = new DisplayIndependentRectangle(getLayoutBounds(entities, true));
bounds.height = bounds2.height;
bounds.y = bounds2.y;
for (int i = 0; i < entities.length; i++) {
InternalNode entity = entities[i];
double percentTheta = (entity.getInternalX() - bounds.x) / bounds.width;
double distance = (entity.getInternalY() - bounds.y) / bounds.height;
double theta = startDegree + Math.abs(endDegree - startDegree) * percentTheta;
double newX = distance * Math.cos(theta);
double newY = distance * Math.sin(theta);
entity.setInternalLocation(newX, newY);
}
}
/**
* Find the bounds in which the nodes are located. Using the bounds against the real bounds
* of the screen, the nodes can proportionally be placed within the real bounds.
* The bounds can be determined either including the size of the nodes or not. If the size
* is not included, the bounds will only be guaranteed to include the center of each node.
*/
protected DisplayIndependentRectangle getLayoutBounds(InternalNode[] entitiesToLayout, boolean includeNodeSize) {
DisplayIndependentRectangle layoutBounds = super.getLayoutBounds(entitiesToLayout, includeNodeSize);
DisplayIndependentPoint centerPoint = (roots != null) ? determineCenterPoint(roots) : new DisplayIndependentPoint(layoutBounds.x + layoutBounds.width / 2, layoutBounds.y + layoutBounds.height / 2);
// The center entity is determined in applyLayout
double maxDistanceX = Math.max(Math.abs(layoutBounds.x + layoutBounds.width - centerPoint.x), Math.abs(centerPoint.x - layoutBounds.x));
double maxDistanceY = Math.max(Math.abs(layoutBounds.y + layoutBounds.height - centerPoint.y), Math.abs(centerPoint.y - layoutBounds.y));
layoutBounds = new DisplayIndependentRectangle(centerPoint.x - maxDistanceX, centerPoint.y - maxDistanceY, maxDistanceX * 2, maxDistanceY * 2);
return layoutBounds;
}
/**
* Find the center point between the roots
*/
private DisplayIndependentPoint determineCenterPoint(List roots) {
double totalX = 0, totalY = 0;
for (Iterator iterator = roots.iterator(); iterator.hasNext();) {
InternalNode entity = (InternalNode) iterator.next();
totalX += entity.getInternalX();
totalY += entity.getInternalY();
}
return new DisplayIndependentPoint(totalX / roots.size(), totalY / roots.size());
}
}