package jas.plot;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
public final class DataAreaLayout implements LayoutManager2
{
public static final String X_AXIS = "x";
public static final String Y_AXIS_LEFT = "yl";
public static final String Y_AXIS_RIGHT = "yr";
public static final String X_AXIS_LABEL = "xl";
public static final String Y_AXIS_LEFT_LABEL = "yll";
public static final String Y_AXIS_RIGHT_LABEL = "yrl";
public void addLayoutComponent(final Component c, final Object constraints)
{
if (c instanceof Axis)
{
if (X_AXIS.equals(constraints)) {xAxis = (Axis) c; xAxisLength = -1;}
else if (Y_AXIS_LEFT.equals(constraints)) {yAxis_left = (Axis) c; yLeftAxisLength = -1;}
else if (Y_AXIS_RIGHT.equals(constraints)) {
yAxis_right = (Axis) c; yRightAxisLength = -1;
if ( c instanceof ColorMapAxis )
hasColorMap = true;
}
}
else if (c instanceof EditableLabel)
{
if (X_AXIS_LABEL.equals(constraints)) xAxis_label = (EditableLabel) c;
else if (Y_AXIS_LEFT_LABEL.equals(constraints)) yAxis_left_label = (EditableLabel) c;
else if (Y_AXIS_RIGHT_LABEL.equals(constraints)) yAxis_right_label = (EditableLabel) c;
}
}
public void addLayoutComponent(final String s, final Component c)
{
addLayoutComponent(c,s);
}
public void removeLayoutComponent(final Component c)
{
if (c == xAxis) xAxis = null;
else if (c == yAxis_left) yAxis_left = null;
else if (c == yAxis_right) {
yAxis_right = null;
if ( hasColorMap)
hasColorMap = false;
}
else if (c == xAxis_label) xAxis_label = null;
else if (c == yAxis_left_label) yAxis_left_label = null;
else if (c == yAxis_right_label) yAxis_right_label = null;
}
private int getLabelSpaceOnTop()
{
int labelSpaceOnTop = 0;
if (yAxis_left_label != null && ! yAxis_left_label.isRotated() )
labelSpaceOnTop = yAxis_left_label.getPreferredSize().height + pad;
if (yAxis_right_label != null && /* no label without matching axis */ yAxis_right != null && ! yAxis_right_label.isRotated() )
labelSpaceOnTop = Math.max(yAxis_right_label.getPreferredSize().height, labelSpaceOnTop);
return labelSpaceOnTop;
}
public void layoutContainer(final Container parent)
{
if (yAxis_left != null && xAxis != null) // there must be a left axis; right one optional
{
final Dimension parentSize = parent.getSize();
if (parentSize.width <= 0 || parentSize.height <= 0) return; // not sure why this is necessary
final int labelSpaceOnTop = getLabelSpaceOnTop();
final int labelSpaceOnBottom = xAxis_label != null ? xAxis_label.getPreferredSize().height + pad : 0;
final Insets insets = parent.getInsets();
final int width = parentSize.width - insets.right - insets.left - (hasColorMap ? 30 : 0);
final int height = parentSize.height - insets.top - insets.bottom - labelSpaceOnTop - labelSpaceOnBottom;
// we know that the axes always use the same objects for space requirements, so we can obtain
// final references to them now and have them calculate values later as many times as we need
final SpaceRequirements x = xAxis.type.spaceRequirements;
final SpaceRequirements y_left = yAxis_left.type.spaceRequirements;
final SpaceRequirements y_right = yAxis_right != null ? yAxis_right.type.spaceRequirements : nullSpaceRequirements;
// now we have to set the axis lengths
if (lastParentSize != null && xAxisLength > 0) xAxisLength += parentSize.width - lastParentSize.width;
else xAxisLength = parentSize.width * 9 / 10; // a first estimate
if (lastParentSize != null && yLeftAxisLength > 0) yLeftAxisLength += parentSize.height - lastParentSize.height;
else yLeftAxisLength = parentSize.height * 9 / 10; // a first estimate
if (lastParentSize != null && yRightAxisLength > 0 && yAxis_right != null)
yRightAxisLength += parentSize.height - lastParentSize.height;
else yRightAxisLength = parentSize.height * 9 / 10; // a first estimate
int xorigin;
int yorigin;
int distFromRightSide;
int iterationCounter = 0;
final int maxIterations = 8; // If we do more that this many iterations, we will give up and quit
int x_smallest = 0;
int y_left_smallest = 0;
int y_right_smallest = 0;
final int normalMaximumNumberOfIterations = 2; // If we do more than this many iterations, we have an
// unusual situation, so we will start work in plan B
final int criticalNumberOfIterations = 5; // If we do more than this many iterations, plan A has definitely
// failed so we need to start implement the work we started earlier
// Definitions of plans A and B:
// * In plan A, we hope to get an exact fit. This works in one or two iterations in the vast majority of cases.
// We just keep adjusting the axis lengths until we get that perfect fit.
// * Plan B is our last resort when plan A takes too many iterations. We will take the smallest axis lengths
// from all of the attempts and use those for the lengths. This means that an all axes will assume
// smaller lengths than they actually get, so there will be fewer labels than there could be.
while (true)
{
boolean allLengthsAreAdequate = true;
if (iterationCounter < criticalNumberOfIterations)
// This is the normal case, where we have few iterations so far
{
xAxis.assumeAxisLength(xAxisLength);
yAxis_left.assumeAxisLength(yLeftAxisLength);
if (yAxis_right != null) yAxis_right.assumeAxisLength(yRightAxisLength);
}
else
// We've gone too far for plan A, so we'd better start using the minimum values we've
// been tracking
{
if (debug) System.out.println("******* USED BACKUP PLAN FOR LAYOUT");
xAxis.assumeAxisLength(x_smallest);
yAxis_left.assumeAxisLength(y_left_smallest);
if (yAxis_right != null) yAxis_right.assumeAxisLength(y_right_smallest);
}
xorigin = Math.max(x.width, y_left.width) + Axis.padAroundEdge;
yorigin = Math.max(Math.max(x.height , y_left.height), y_right.height) + Axis.padAroundEdge;
distFromRightSide = Math.max(x.flowPastEnd, y_right.width) + Axis.padAroundEdge;
final int minWidth = xorigin + distFromRightSide + xAxisLength + insets.left + insets.right;
if (minWidth != parentSize.width)
{
xAxisLength = Math.max(0,xAxisLength + parentSize.width - minWidth);
allLengthsAreAdequate = false;
}
int minHeight = yorigin + y_left.flowPastEnd + Axis.padAroundEdge +
yLeftAxisLength + insets.top + insets.bottom;
if (minHeight != parentSize.height)
{
yLeftAxisLength = Math.max(0,yLeftAxisLength + parentSize.height - minHeight);
allLengthsAreAdequate = false;
}
if (yAxis_right != null)
{
minHeight = yorigin + y_right.flowPastEnd + Axis.padAroundEdge
+ yRightAxisLength + insets.top + insets.bottom;
if (minHeight != parentSize.height)
{
yRightAxisLength = Math.max (0,yRightAxisLength + parentSize.height - minHeight);
allLengthsAreAdequate = false;
}
}
iterationCounter++;
if (allLengthsAreAdequate)
{
if (debug) System.out.println("layout required "+ iterationCounter +" iteration(s)");
break;
}
if (iterationCounter > normalMaximumNumberOfIterations)
// If this executes, we've had more than normal number of
// iterations, so we start tracking the sizes to come up with
// a size that will work for all sizes
{
if (xAxisLength > 0)
if (x_smallest == 0) x_smallest = xAxisLength;
else if (xAxisLength < x_smallest) x_smallest = xAxisLength;
if (yLeftAxisLength > 0)
if (y_left_smallest == 0) y_left_smallest = yLeftAxisLength;
else if (yLeftAxisLength < y_left_smallest) y_left_smallest = yLeftAxisLength;
if (yAxis_right != null && yRightAxisLength > 0)
if (y_right_smallest == 0) y_right_smallest = yRightAxisLength;
else if (yRightAxisLength < y_right_smallest) y_right_smallest = yRightAxisLength;
}
if (iterationCounter >= maxIterations) throw new LayoutFailed();
}
if (yAxis_left_label != null && yAxis_left_label.isRotated()) xorigin += yAxis_left_label.getPreferredSize().getHeight()+5;
yAxis_left.setLocation(xorigin - y_left.width + insets.left, insets.top + labelSpaceOnTop);
yAxis_left.setSize(y_left.width, height - yorigin + y_left.height);
if (yAxis_left_label != null)
{
if ( yAxis_left_label.isRotated() ) {
yAxis_left_label.setLocation(pad, pad + insets.top + (height - (int)yAxis_left_label.getPreferredSize().getWidth())/2);
yAxis_left_label.setSize((int)yAxis_left_label.getPreferredSize().getWidth(),(int)yAxis_left_label.getPreferredSize().getWidth());
} else {
yAxis_left_label.setLocation(insets.left + pad , insets.top + pad);
yAxis_left_label.setSize(yAxis_left_label.getPreferredSize());
}
}
if ( yAxis_right_label != null && yAxis_right_label.isRotated() )
distFromRightSide += (int) yAxis_right_label.getPreferredSize().getHeight();
xAxis.setLocation(xorigin - x.width + insets.left, height + insets.top - yorigin + labelSpaceOnTop);
xAxis.setSize(width - xorigin + x.width - distFromRightSide + Axis.padAroundEdge + x.flowPastEnd , x.height);
if (xAxis_label != null)
{
final Dimension prefSize = xAxis_label.getPreferredSize();
xAxis_label.setLocation((width - xorigin - distFromRightSide) / 2 + xorigin - prefSize.width / 2 + insets.left,
parentSize.height - insets.bottom - pad - prefSize.height);
xAxis_label.setSize(prefSize);
}
if (yAxis_right != null)
{
yAxis_right.setLocation(width - distFromRightSide - insets.left ,insets.top + labelSpaceOnTop);
yAxis_right.setSize(y_right.width+(hasColorMap?30:0), height - yorigin + y_right.height);
if (yAxis_right_label != null)
{
final Dimension prefSize = yAxis_right_label.getPreferredSize();
if ( yAxis_right_label.isRotated() ) {
yAxis_right_label.setLocation(parentSize.width - insets.right - pad - prefSize.height , pad + insets.top + (height - (int)prefSize.getWidth())/2);
yAxis_right_label.setSize((int)prefSize.getWidth(), (int) prefSize.getWidth());
} else {
yAxis_right_label.setLocation(parentSize.width - insets.right - pad - prefSize.width , insets.top + pad);
yAxis_right_label.setSize(prefSize);
}
}
}
lastParentSize = parentSize;
}
}
public Dimension minimumLayoutSize(final Container parent)
{
return preferredLayoutSize(parent);
}
public Dimension maximumLayoutSize(final Container parent)
{
return preferredLayoutSize(parent);
}
public Dimension preferredLayoutSize(final Container parent)
{
return new Dimension(10,10);
}
/**
* Returns the alignment along the x axis. This specifies how
* the component would like to be aligned relative to other
* components. The value should be a number between 0 and 1
* where 0 represents alignment along the origin, 1 is aligned
* the furthest away from the origin, 0.5 is centered, etc.
*/
public float getLayoutAlignmentX(final Container parent)
{
return 0.5f;
}
/**
* Returns the alignment along the y axis. This specifies how
* the component would like to be aligned relative to other
* components. The value should be a number between 0 and 1
* where 0 represents alignment along the origin, 1 is aligned
* the furthest away from the origin, 0.5 is centered, etc.
*/
public float getLayoutAlignmentY(final Container parent)
{
return 0.5f;
}
/**
* Invalidates the layout, indicating that if the layout manager
* has cached information it should be discarded.
*/
public void invalidateLayout(final Container target)
{
}
private Axis xAxis;
private Axis yAxis_left;
private Axis yAxis_right;
private EditableLabel xAxis_label;
private EditableLabel yAxis_left_label;
private EditableLabel yAxis_right_label;
private static final int pad = 5;
private static final SpaceRequirements nullSpaceRequirements = new SpaceRequirements(); // all fields are zero
private int xAxisLength = -1;
private int yLeftAxisLength = -1;
private int yRightAxisLength = -1;
private boolean hasColorMap = false;
private Dimension lastParentSize;
private static final boolean debug;
static
{
boolean result;
try
{
result = System.getProperty("debugDataAreaLayout") != null;
}
catch (SecurityException x) // in case we are in an applet!
{
result = false;
}
debug = result;
}
}
class LayoutFailed extends RuntimeException
{
}