/*******************************************************************************
* Copyright (c) 2005, 2016 The Chisel Group 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: Casey Best, Ian Bull, Rob Lintern (The Chisel Group) - initial API and implementation
* Mateusz Matela - "Tree Views for Zest" contribution, Google Summer of Code 2009
* Matthias Wienand (itemis AG) - refactorings
******************************************************************************/
package org.eclipse.gef.layout.algorithms;
import org.eclipse.gef.geometry.planar.Dimension;
import org.eclipse.gef.geometry.planar.Point;
import org.eclipse.gef.geometry.planar.Rectangle;
import org.eclipse.gef.graph.Node;
import org.eclipse.gef.layout.ILayoutAlgorithm;
import org.eclipse.gef.layout.LayoutContext;
import org.eclipse.gef.layout.LayoutProperties;
/**
* The {@link GridLayoutAlgorithm} lays out nodes in a grid.
*
* @author Casey Best
* @author Ian Bull
* @author Rob Lintern
* @author Mateusz Matela
* @author mwienand
*/
public class GridLayoutAlgorithm implements ILayoutAlgorithm {
private static final double PADDING_PERCENTAGE = 0.95;
private static final int MIN_ENTITY_SIZE = 5;
/**
* The width/height ratio.
*/
private double aspectRatio = 1.0;
/**
* The padding around rows.
*/
private int rowPadding = 0;
private boolean resize = false;
/**
* The number of rows.
*/
private int rows;
/**
* The number of columns.
*/
private int cols;
/**
* The number of nodes.
*/
private int numChildren;
/**
* The column width.
*/
private double colWidth;
/**
* The row height.
*/
private double rowHeight;
/**
* The horizontal offset.
*/
private double offsetX;
/**
* The vertical offset.
*/
private double offsetY;
/**
* The height of a single node.
*/
private double childrenHeight;
/**
* The width of a single node.
*/
private double childrenWidth;
/**
* Default constructor.
*/
public GridLayoutAlgorithm() {
}
public void applyLayout(LayoutContext context, boolean clean) {
if (!clean)
return;
numChildren = context.getNodes().length;
Rectangle bounds = LayoutProperties.getBounds(context.getGraph());
calculateGrid(bounds);
int index = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if ((i * cols + j) < numChildren) {
Node node = context.getNodes()[index++];
if (resize && LayoutProperties.isResizable(node))
LayoutProperties.setSize(node, new Dimension(
Math.max(childrenWidth, MIN_ENTITY_SIZE),
Math.max(childrenHeight, MIN_ENTITY_SIZE)));
Dimension size = LayoutProperties.getSize(node);
double xmove = bounds.getX() + j * colWidth + offsetX
+ size.width / 2;
double ymove = bounds.getY() + i * rowHeight + offsetY
+ size.height / 2;
if (LayoutProperties.isMovable(node))
LayoutProperties.setLocation(node,
new Point(xmove, ymove));
}
}
}
}
/**
* Calculates all the dimensions of grid that layout entities will be fit
* in.
*
* @param bounds
* A {@link Rectangle} representing the layout bounds.
*/
// The following fields are set by this method: {@link #numChildren},
// {@link #rows}, {@link #cols}, {@link #colWidth}, {@link #rowHeight},
// {@link #offsetX}, {@link #offsetY}
protected void calculateGrid(Rectangle bounds) {
int[] result = calculateNumberOfRowsAndCols(numChildren, bounds.getX(),
bounds.getY(), bounds.getWidth(), bounds.getHeight());
cols = result[0];
rows = result[1];
colWidth = bounds.getWidth() / cols;
rowHeight = bounds.getHeight() / rows;
double[] nodeSize = calculateNodeSize(colWidth, rowHeight);
childrenWidth = nodeSize[0];
childrenHeight = nodeSize[1];
offsetX = (colWidth - childrenWidth) / 2.0; // half of the space between
// columns
offsetY = (rowHeight - childrenHeight) / 2.0; // half of the space
// between rows
}
/**
* Calculates and returns an array containing the number of columns and the
* number of rows. If the {@link #setAspectRatio(double) aspect ratio} is
* set to <code>1</code>, then the
* {@link #calculateNumberOfRowsAndCols_square(int, double, double, double, double)}
* method is used for the computation. Otherwise, the
* {@link #calculateNumberOfRowsAndCols_rectangular(int)} is used for the
* computation.
*
* @param numChildren
* The number of nodes.
* @param boundX
* The horizontal offset.
* @param boundY
* The vertical offset.
* @param boundWidth
* The bounds' width.
* @param boundHeight
* The bounds' height.
* @return An array containing the number of columns, and the number of
* rows.
*/
protected int[] calculateNumberOfRowsAndCols(int numChildren, double boundX,
double boundY, double boundWidth, double boundHeight) {
if (aspectRatio == 1.0) {
return calculateNumberOfRowsAndCols_square(numChildren, boundX,
boundY, boundWidth, boundHeight);
} else {
return calculateNumberOfRowsAndCols_rectangular(numChildren);
}
}
/**
* Calculates and returns an array containing the number of columns and the
* number of rows, so that the nodes are layed out in squares.
*
* @param numChildren
* The number of nodes.
* @param boundX
* The horizontal offset.
* @param boundY
* The vertical offset.
* @param boundWidth
* The bounds' width.
* @param boundHeight
* The bounds' height.
* @return An array containing the number of columns and the number of rows.
*/
protected int[] calculateNumberOfRowsAndCols_square(int numChildren,
double boundX, double boundY, double boundWidth,
double boundHeight) {
int rows = Math.max(1,
(int) Math.sqrt(numChildren * boundHeight / boundWidth));
int cols = Math.max(1,
(int) Math.sqrt(numChildren * boundWidth / boundHeight));
// if space is taller than wide, adjust rows first
if (boundWidth <= boundHeight) {
// decrease number of rows and columns until just enough or not
// enough
while (rows * cols > numChildren) {
if (rows > 1)
rows--;
if (rows * cols > numChildren)
if (cols > 1)
cols--;
}
// increase number of rows and columns until just enough
while (rows * cols < numChildren) {
rows++;
if (rows * cols < numChildren)
cols++;
}
} else {
// decrease number of rows and columns until just enough or not
// enough
while (rows * cols > numChildren) {
if (cols > 1)
cols--;
if (rows * cols > numChildren)
if (rows > 1)
rows--;
}
// increase number of rows and columns until just enough
while (rows * cols < numChildren) {
cols++;
if (rows * cols < numChildren)
rows++;
}
}
int[] result = { cols, rows };
return result;
}
/**
* Calculates and returns an array containing the number of columns and the
* number of rows, so that there is an equal number of rows and columns.
*
* @param numChildren
* The number of nodes.
* @return An array containing the number of columns and the number of rows.
*/
protected int[] calculateNumberOfRowsAndCols_rectangular(int numChildren) {
int rows = Math.max(1, (int) Math.ceil(Math.sqrt(numChildren)));
int cols = Math.max(1, (int) Math.ceil(Math.sqrt(numChildren)));
int[] result = { cols, rows };
return result;
}
/**
* Calculates and returns the width and height of a single node depending on
* the {@link #setRowPadding(int) padding} (20%), <i>colWidth</i>,
* <i>rowHeight</i>, and {@link #setAspectRatio(double) aspect ratio}.
*
* @param colWidth
* The width of a column.
* @param rowHeight
* The height of a row.
* @return An array containing the width and height of a node.
*/
protected double[] calculateNodeSize(double colWidth, double rowHeight) {
double childW = Math.max(MIN_ENTITY_SIZE,
PADDING_PERCENTAGE * colWidth);
double childH = Math.max(MIN_ENTITY_SIZE,
PADDING_PERCENTAGE * (rowHeight - rowPadding));
double whRatio = colWidth / rowHeight;
if (whRatio < aspectRatio) {
childH = childW / aspectRatio;
} else {
childW = childH * aspectRatio;
}
double[] result = { childW, childH };
return result;
}
/**
* Sets the padding between rows in the grid
*
* @param rowPadding
* padding - should be greater than or equal to 0
*/
public void setRowPadding(int rowPadding) {
if (rowPadding >= 0) {
this.rowPadding = rowPadding;
}
}
/**
* Sets the preferred aspect ratio for layout entities. The default aspect
* ratio is 1.
*
* @param aspectRatio
* aspect ratio - should be greater than 0
*/
public void setAspectRatio(double aspectRatio) {
if (aspectRatio > 0) {
this.aspectRatio = aspectRatio;
}
}
/**
*
* @return true if this algorithm is set to resize elements
*/
public boolean isResizing() {
return resize;
}
/**
*
* @param resizing
* true if this algorithm should resize elements (default is
* false)
*/
public void setResizing(boolean resizing) {
resize = resizing;
}
}