/******************************************************************************* * 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.Arrays; import org.eclipse.zest.layouts.LayoutStyles; import org.eclipse.zest.layouts.dataStructures.InternalNode; import org.eclipse.zest.layouts.dataStructures.InternalRelationship; /** * @version 2.0 * @author Ian Bull * @author Casey Best and Rob Lintern */ public class GridLayoutAlgorithm extends AbstractLayoutAlgorithm { private static final double PADDING_PERCENTAGE = 0.95; protected int rowPadding = 0; public void setLayoutArea(double x, double y, double width, double height) { throw new RuntimeException("Operation not implemented"); } int rows, cols, numChildren; double colWidth, rowHeight, offsetX, offsetY; int totalProgress; double h, w; /** * Initializes the grid layout. * @param styles * @see LayoutStyles */ public GridLayoutAlgorithm(int styles) { super(styles); } /** * Inititalizes the grid layout with no style. */ public GridLayoutAlgorithm() { this(LayoutStyles.NONE); } protected int getCurrentLayoutStep() { // TODO: This isn't right return 0; } protected int getTotalNumberOfLayoutSteps() { return totalProgress; } /** * */ protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) { // TODO: Filter unwanted entities and relationships //super.applyLayout (entitiesToLayout, relationshipsToConsider, boundsX, boundsY, boundsWidth, boundsHeight); // now begin numChildren = entitiesToLayout.length; if (numChildren < 1) return; int[] colsAndRows = calculateNumberOfRowsAndCols(numChildren, x, y, width, height); cols = colsAndRows[0]; rows = colsAndRows[1]; totalProgress = rows + 2; fireProgressEvent (1, totalProgress); // sort the entities if (comparator != null) { Arrays.sort(entitiesToLayout, comparator); } else { Arrays.sort(entitiesToLayout); } fireProgressEvent (2, totalProgress); // Calculate row height and column width colWidth = width/cols; rowHeight = height/rows; // Calculate amount to scale children double [] nodeSize = calculateNodeSize (colWidth, rowHeight); w = nodeSize[0]; h = nodeSize[1]; offsetX = (colWidth - w)/2.0; // half of the space between columns offsetY = (rowHeight - h)/2.0; // half of the space between rows } /** * Use this algorithm to layout the given entities, using the given relationships and bounds. * The entities will be placed in the same order as they are passed in, unless a comparator * is supplied. * * @param entitiesToLayout Apply the algorithm to these entities * @param relationshipsToConsider Only consider these relationships when applying the algorithm. * @param boundsX The left side of the bounds in which the layout can place the entities. * @param boundsY The top side of the bounds in which the layout can place the entities. * @param boundsWidth The width of the bounds in which the layout can place the entities. * @param boundsHeight The height of the bounds in which the layout can place the entities. * @throws RuntimeException Thrown if entitiesToLayout doesn't contain all of the endpoints for each relationship in relationshipsToConsider */ protected synchronized void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight) { int index = 0; for( int i = 0; i < rows; i++ ) { for( int j = 0; j < cols; j++ ) { if( (i*cols + j) < numChildren ) { // find new position for child double xmove = boundsX + j * colWidth + offsetX; double ymove = boundsY + i * rowHeight + offsetY; InternalNode sn = entitiesToLayout[index++]; sn.setInternalLocation( xmove, ymove ); sn.setInternalSize( Math.max(w, MIN_ENTITY_SIZE), Math.max(h, MIN_ENTITY_SIZE) ); } } fireProgressEvent (2 + i, totalProgress); } updateLayoutLocations(entitiesToLayout); fireProgressEvent (totalProgress, totalProgress); } protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) { } /** * Calculates and returns an array containing the number of columns, followed by the number of rows */ protected int[] calculateNumberOfRowsAndCols (int numChildren, double boundX, double boundY, double boundWidth, double boundHeight) { if (getEntityAspectRatio() == 1.0) { return calculateNumberOfRowsAndCols_square (numChildren, boundX, boundY, boundWidth, boundHeight); } else { return calculateNumberOfRowsAndCols_rectangular (numChildren); } } 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; } 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; } 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 < getEntityAspectRatio()) { childH = childW/getEntityAspectRatio(); } else { childW = childH*getEntityAspectRatio(); } double [] result = {childW, childH}; return result; } /** * Increases the padding between rows in the grid * @param rowPadding Value will not be set if less than 0. */ public void setRowPadding(int rowPadding) { if (rowPadding < 0 ) { return; } this.rowPadding = rowPadding; } 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; } }