Skip to content

Instantly share code, notes, and snippets.

@bysse
Created September 19, 2013 17:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bysse/6627232 to your computer and use it in GitHub Desktop.
Save bysse/6627232 to your computer and use it in GitHub Desktop.
A JFreeChart layout for both the vertical and the horizontal axis. It works by specifying the width, in items, of the initial column. The column is filled with as many labels as there is room for, then a new column will be created with the same width. The arrangement works the same way for rows. http://blog.slackers.se/2009/07/jfreechart-legend-…
package se.slackers.jfreechart;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jfree.chart.block.Arrangement;
import org.jfree.chart.block.Block;
import org.jfree.chart.block.BlockContainer;
import org.jfree.chart.block.FlowArrangement;
import org.jfree.chart.block.LengthConstraintType;
import org.jfree.chart.block.RectangleConstraint;
import org.jfree.ui.Size2D;
/**
* The class is used for a more configurable flow layout than {@link FlowArrangement} with both
* horizontal and vertical layout is supported. The initalLimit parameter in the constructor is used
* for telling how many initial columns/row that will be used when doing the layout. But if the
* maximum width or maximum height is reached additional column/rows will be added.
* <p>
* For example, if were specifying a limit of two for a column arrangement.
*
* <pre>
* new ColumnFlowArrangement(2, true);
* </pre>
*
* The class will start to arrange the blocks row by row in two columns. When or if the maximum
* height is reached two additional column will be added and filled.
* </p>
*
* @author erik.b
*/
public class ColumnFlowArrangement implements Arrangement {
public static final int NONE = 0;
private int initialLimit;
private boolean limitOnColumns;
private Size2D margin;
/**
* Creates an instance of ColumnFlowArrangement.
*
* @param initialLimit
* The number of initial columns/rows.
* @param limitOnColumns
* If the limitation should apply to columns(true) or rows(false).
*/
public ColumnFlowArrangement(final int initialLimit, final boolean limitOnColumns) {
this.initialLimit = initialLimit;
this.limitOnColumns = limitOnColumns;
this.margin = new Size2D(5, 5);
}
public void add(final Block block, final Object object) {
}
public void clear() {
}
/**
* Arranges the blocks within a container.
*
* @param container
* The container to arrange.
* @param graphics
* The graphics context to use during the arrangement.
* @param constraint
* A fixed size
* <tt>RectangleConstraint<tt> that specifies the maximum width and height.
* @return The size of the container after the arrangement.
* @throws IllegalArgumentException
* If the constraint doesn't follow the contract.
*/
@SuppressWarnings("unchecked")
public Size2D arrange(final BlockContainer container, final Graphics2D graphics,
final RectangleConstraint constraint) {
// validate the constraint
if (constraint.getWidthConstraintType() != LengthConstraintType.FIXED
&& constraint.getHeightConstraintType() != LengthConstraintType.FIXED) {
throw new IllegalArgumentException(ColumnFlowArrangement.class.getName()
+ " does only supports constraints of type FIXED.");
}
// make sure the container has contents
List<Block> blocks = container.getBlocks();
if (blocks.isEmpty()) {
return new Size2D();
}
if (limitOnColumns) {
return limitedColumnLayout(blocks, graphics, constraint);
} else {
return rowLayout(blocks, graphics, constraint);
}
}
private Size2D rowLayout(final List<Block> blocks, final Graphics2D graphics, final RectangleConstraint constraint) {
Map<Block, Size2D> sizeMap = calculateSizes(blocks, graphics);
Size2D totalSize = new Size2D();
Size2D elementSize = getMaxDimensions(blocks, sizeMap);
int x = 0;
int y = 0;
int startRow = 0;
int row = startRow;
/*
* If the row limit is one, the layout should try to organize the blocks in fixed size
* columns.
*/
if (initialLimit == 1) {
int length = 0;
for (Block block : blocks) {
length += sizeMap.get(block).width + margin.width;
}
// check if all block will fit on one line
if (length <= constraint.getWidth()) {
return oneRowLayout(blocks, graphics, constraint, sizeMap);
}
}
double columnWidth = elementSize.width + margin.width;
// layout/arrange each block
for (Block block : blocks) {
// check if another column needs to be created
if (x + columnWidth > constraint.getWidth()) {
startRow += initialLimit;
row = startRow;
x = 0;
y += margin.height;
}
int xp = x;
int yp = y + (int) (row * elementSize.height);
// set the size and position of a block
Size2D size = sizeMap.get(block);
Rectangle2D bounds = new Rectangle2D.Double(xp, yp, size.width, size.height);
block.setBounds(bounds);
// adjust the total size of the legend
totalSize.width = Math.max(totalSize.width, xp + size.width);
totalSize.height = Math.max(totalSize.height, yp + size.height);
row++;
if (row >= startRow + initialLimit) {
row = startRow;
x += columnWidth;
}
}
return totalSize;
}
private Size2D limitedColumnLayout(final List<Block> blocks, final Graphics2D graphics,
final RectangleConstraint constraint) {
Map<Block, Size2D> sizeMap = calculateSizes(blocks, graphics);
Size2D totalSize = new Size2D();
Size2D elementSize = getMaxDimensions(blocks, sizeMap);
int x = 0;
int y = 0;
int startColumn = 0;
int column = startColumn;
double rowHeight = elementSize.height + margin.height;
for (Block block : blocks) {
// check if another column needs to be created
if (y+rowHeight > constraint.getHeight()) {
startColumn += initialLimit;
column = startColumn;
x += margin.width;
y = 0;
}
int xp = x + (int) (column * elementSize.width);
int yp = y;
// set the size and position of a block
Size2D size = sizeMap.get(block);
Rectangle2D bounds = new Rectangle2D.Double(xp, yp, size.width, size.height);
block.setBounds(bounds);
// adjust the total size of the legend
totalSize.width = Math.max(totalSize.width, xp + size.width);
totalSize.height = Math.max(totalSize.height, yp + size.height);
column++;
if (column >= startColumn + initialLimit) {
column = startColumn;
y += rowHeight;
}
}
return totalSize;
}
private Size2D oneRowLayout(final List<Block> blocks, final Graphics2D graphics,
final RectangleConstraint constraint, final Map<Block, Size2D> sizeMap) {
Size2D totalSize = new Size2D();
int x = 0;
int y = 0;
for (Block block : blocks) {
Size2D size = sizeMap.get(block);
Rectangle2D bounds = new Rectangle2D.Double(x, y, size.width, size.height);
block.setBounds(bounds);
x += size.width;
// adjust the total size of the legend
totalSize.width = Math.max(totalSize.width, x + size.width);
totalSize.height = Math.max(totalSize.height, y + size.height);
}
return totalSize;
}
private Size2D getMaxDimensions(final List<Block> blocks, final Map<Block, Size2D> sizeMap) {
Size2D maxSize = new Size2D();
for (Block block : blocks) {
Size2D size = sizeMap.get(block);
maxSize.width = Math.max(size.width, maxSize.width);
maxSize.height = Math.max(size.height, maxSize.height);
}
return maxSize;
}
private Map<Block, Size2D> calculateSizes(final List<Block> blocks, final Graphics2D graphics) {
Map<Block, Size2D> sizeMap = new HashMap<Block, Size2D>(blocks.size());
for (Block block : blocks) {
sizeMap.put(block, block.arrange(graphics));
}
return sizeMap;
}
/**
* Return the margin between blocks in the arrangement.
*
* @return the margin
*/
public Size2D getMargin() {
return margin;
}
/**
* Sets the margin between block in the arrangement.
*
* @param margin
* the margin to set
*/
public void setMargin(final Size2D margin) {
this.margin = margin;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment