package com.positive.charts.axis;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import com.positive.charts.axis.ticks.ITick;
import com.positive.charts.axis.ticks.TickUnitSource;
import com.positive.charts.axis.ticks.ValueTick;
import com.positive.charts.block.RectangleInsets;
import com.positive.charts.common.RectangleEdge;
import com.positive.charts.data.Range;
import com.positive.charts.event.AxisChangeEvent;
import com.positive.charts.plot.Plot;
import com.positive.charts.util.TextUtilities;
/**
* The base class for axes that display value data, where values are measured
* using the <code>double</code> primitive. The two key subclasses are
* {@link DateAxis} and {@link NumberAxis}.
*/
public abstract class ValueAxis extends BaseAxis {
/** The default axis range. */
public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
/** The default auto-range value. */
public static final boolean DEFAULT_AUTO_RANGE = true;
/** The default inverted flag setting. */
public static final boolean DEFAULT_INVERTED = false;
/** The default minimum auto range. */
public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
/** The default value for the lower margin (0.05 = 5%). */
public static final double DEFAULT_LOWER_MARGIN = 0.05;
/** The default value for the upper margin (0.05 = 5%). */
public static final double DEFAULT_UPPER_MARGIN = 0.05;
/** The default lower bound for the axis. */
public static final double DEFAULT_LOWER_BOUND = 0.0;
/** The default upper bound for the axis. */
public static final double DEFAULT_UPPER_BOUND = 1.0;
/** The default auto-tick-unit-selection value. */
public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
/** The maximum tick count. */
public static final int MAXIMUM_TICK_COUNT = 500;
/**
* A flag that controls whether an arrow is drawn at the positive end of the
* axis line.
*/
private boolean positiveArrowVisible;
/**
* A flag that controls whether an arrow is drawn at the negative end of the
* axis line.
*/
private boolean negativeArrowVisible;
/** A flag that affects the orientation of the values on the axis. */
private boolean inverted;
/** The axis range. */
private Range range;
/**
* Flag that indicates whether the axis automatically scales to fit the
* chart data.
*/
private boolean autoRange;
/** The minimum size for the 'auto' axis range (excluding margins). */
private double autoRangeMinimumSize;
/**
* The upper margin percentage. This indicates the amount by which the
* maximum axis value exceeds the maximum data value (as a percentage of the
* range on the axis) when the axis range is determined automatically.
*/
private double upperMargin;
/**
* The lower margin. This is a percentage that indicates the amount by which
* the minimum axis value is "less than" the minimum data value when the
* axis range is determined automatically.
*/
private double lowerMargin;
/**
* If this value is positive, the amount is subtracted from the maximum data
* value to determine the lower axis range. This can be used to provide a
* fixed "window" on dynamic data.
*/
private double fixedAutoRange;
/**
* Flag that indicates whether or not the tick unit is selected
* automatically.
*/
private boolean autoTickUnitSelection;
/** The standard tick units for the axis. */
private TickUnitSource standardTickUnits;
/** An index into an array of standard tick values. */
private int autoTickIndex;
/** A flag indicating whether or not tick labels are rotated to vertical. */
private boolean verticalTickLabels;
/**
* Constructs a value axis.
*
* @param label
* the axis label.
* @param standardTickUnits
* the source for standard tick units (<code>null</code>
* permitted).
*/
protected ValueAxis(final String label,
final TickUnitSource standardTickUnits) {
super(label);
this.positiveArrowVisible = false;
this.negativeArrowVisible = false;
this.range = DEFAULT_RANGE;
this.autoRange = DEFAULT_AUTO_RANGE;
this.inverted = DEFAULT_INVERTED;
this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
this.lowerMargin = DEFAULT_LOWER_MARGIN;
this.upperMargin = DEFAULT_UPPER_MARGIN;
this.fixedAutoRange = 0.0;
this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
this.standardTickUnits = standardTickUnits;
this.verticalTickLabels = false;
}
/**
* Automatically sets the axis range to fit the range of values in the
* dataset. Sometimes this can depend on the renderer used as well (for
* example, the renderer may "stack" values, requiring an axis range greater
* than otherwise necessary).
*/
protected abstract void autoAdjustRange();
/**
* Calculates the anchor point for a tick label.
*
* @param tick
* the tick.
* @param cursor
* the cursor.
* @param dataArea
* the data area.
* @param edge
* the edge on which the axis is drawn.
*
* @return The anchor point.
*/
protected Point calculateAnchorPoint(final ValueTick tick,
final double cursor, final Rectangle dataArea,
final RectangleEdge edge) {
final RectangleInsets insets = this.getTickLabelInsets();
// FIXME Convert these routines to use integers
final float[] result = new float[2];
if (edge == RectangleEdge.TOP) {
result[0] = (float) this.valueToJava2D(tick.getValue(), dataArea,
edge);
result[1] = (float) (cursor - insets.getBottom() - 2.0);
} else if (edge == RectangleEdge.BOTTOM) {
result[0] = (float) this.valueToJava2D(tick.getValue(), dataArea,
edge);
result[1] = (float) (cursor + insets.getTop() + 2.0);
} else if (edge == RectangleEdge.LEFT) {
result[0] = (float) (cursor - insets.getLeft() - 2.0);
result[1] = (float) this.valueToJava2D(tick.getValue(), dataArea,
edge);
} else if (edge == RectangleEdge.RIGHT) {
result[0] = (float) (cursor + insets.getRight() + 2.0);
result[1] = (float) this.valueToJava2D(tick.getValue(), dataArea,
edge);
}
return new Point((int) result[0], (int) result[1]);
}
/**
* Centers the axis range about the specified value and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param value
* the center value.
*/
public void centerRange(final double value) {
final double central = this.range.getCentralValue();
final Range adjusted = new Range(this.range.getLowerBound() + value
- central, this.range.getUpperBound() + value - central);
this.setRange(adjusted);
}
/**
* Draws an axis line at the current cursor position and edge.
*
* @param g2
* the graphics device.
* @param cursor
* the cursor position. TODO Use integers
* @param dataArea
* the data area.
* @param edge
* the edge.
*/
protected void drawAxisLine(final GC g2, final double cursor,
final Rectangle dataArea, final RectangleEdge edge) {
// TODO Set line style and color
// g2.setPaint(getAxisLinePaint());
// g2.setStroke(getAxisLineStroke());
if (edge.isTopOrBottom()) {
g2.drawLine(dataArea.x, (int) cursor, dataArea.x + dataArea.width,
(int) cursor);
} else {
g2.drawLine((int) cursor, dataArea.y, (int) cursor, dataArea.y
+ dataArea.height);
}
boolean drawUpOrRight = false;
boolean drawDownOrLeft = false;
if (this.positiveArrowVisible) {
if (this.inverted) {
drawDownOrLeft = true;
} else {
drawUpOrRight = true;
}
}
if (this.negativeArrowVisible) {
if (this.inverted) {
drawUpOrRight = true;
} else {
drawDownOrLeft = true;
}
}
if (drawUpOrRight) {
// double x = 0.0;
// double y = 0.0;
// Shape arrow = null;
// if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
// x = dataArea.getMaxX();
// y = cursor;
// arrow = this.rightArrow;
// } else if (edge == RectangleEdge.LEFT
// || edge == RectangleEdge.RIGHT) {
// x = cursor;
// y = dataArea.getMinY();
// arrow = this.upArrow;
// }
//
// // draw the arrow...
// AffineTransform transformer = new AffineTransform();
// transformer.setToTranslation(x, y);
// Shape shape = transformer.createTransformedShape(arrow);
// g2.fill(shape);
// g2.draw(shape);
}
if (drawDownOrLeft) {
// double x = 0.0;
// double y = 0.0;
// Shape arrow = null;
// if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
// x = dataArea.getMinX();
// y = cursor;
// arrow = this.leftArrow;
// } else if (edge == RectangleEdge.LEFT
// || edge == RectangleEdge.RIGHT) {
// x = cursor;
// y = dataArea.getMaxY();
// arrow = this.downArrow;
// }
//
// // draw the arrow...
// AffineTransform transformer = new AffineTransform();
// transformer.setToTranslation(x, y);
// Shape shape = transformer.createTransformedShape(arrow);
// g2.fill(shape);
// g2.draw(shape);
}
}
/**
* Draws the axis line, tick marks and tick mark labels.
*
* @param g2
* the graphics device.
* @param cursor
* the cursor.
* @param plotArea
* the plot area.
* @param dataArea
* the data area.
* @param edge
* the edge that the axis is aligned with.
*
* @return The width or height used to draw the axis.
*/
protected AxisState drawTickMarksAndLabels(final GC g2,
final double cursor, final Rectangle plotArea,
final Rectangle dataArea, final RectangleEdge edge) {
final AxisState state = new AxisState(cursor);
if (this.isAxisLineVisible()) {
this.drawAxisLine(g2, cursor, dataArea, edge);
}
final double ol = this.getTickMarkOutsideLength();
final double il = this.getTickMarkInsideLength();
final List ticks = this.refreshTicks(g2, state, dataArea, edge);
state.setTicks(ticks);
g2.setFont(this.getTickLabelFont());
final Iterator iterator = ticks.iterator();
while (iterator.hasNext()) {
final ValueTick tick = (ValueTick) iterator.next();
if (this.isTickLabelsVisible()) {
// TODO Set color
// g2.setPaint(getTickLabelPaint());
final Point anchorPoint = this.calculateAnchorPoint(tick,
cursor, dataArea, edge);
TextUtilities.drawRotatedString(tick.getText(), g2,
anchorPoint.x, anchorPoint.y, tick.getTextAnchor(),
tick.getAngle(), tick.getRotationAnchor());
}
if (this.isTickMarksVisible()) {
final int xx = (int) this.valueToJava2D(tick.getValue(),
dataArea, edge);
// TODO Set color and linestyle
// g2.setStroke(getTickMarkStroke());
// g2.setPaint(getTickMarkPaint());
// TODO Use integers
if (edge == RectangleEdge.LEFT) {
g2.drawLine((int) (cursor - ol), xx, (int) (cursor + il),
xx);
} else if (edge == RectangleEdge.RIGHT) {
g2.drawLine((int) (cursor + ol), xx, (int) (cursor - il),
xx);
} else if (edge == RectangleEdge.TOP) {
g2.drawLine(xx, (int) (cursor - ol), xx,
(int) (cursor + il));
} else if (edge == RectangleEdge.BOTTOM) {
g2.drawLine(xx, (int) (cursor + ol), xx,
(int) (cursor - il));
}
}
}
// need to work out the space used by the tick labels...
// so we can update the cursor...
double used = 0.0;
if (this.isTickLabelsVisible()) {
if (edge == RectangleEdge.LEFT) {
used += this.findMaximumTickLabelWidth(ticks, g2, plotArea,
this.isVerticalTickLabels());
state.cursorLeft(used);
} else if (edge == RectangleEdge.RIGHT) {
used = this.findMaximumTickLabelWidth(ticks, g2, plotArea, this
.isVerticalTickLabels());
state.cursorRight(used);
} else if (edge == RectangleEdge.TOP) {
used = this.findMaximumTickLabelHeight(ticks, g2, plotArea,
this.isVerticalTickLabels());
state.cursorUp(used);
} else if (edge == RectangleEdge.BOTTOM) {
used = this.findMaximumTickLabelHeight(ticks, g2, plotArea,
this.isVerticalTickLabels());
state.cursorDown(used);
}
}
return state;
}
/**
* A utility method for determining the height of the tallest tick label.
*
* @param ticks
* the ticks.
* @param g2
* the graphics device.
* @param drawArea
* the area within which the plot and axes should be drawn.
* @param vertical
* a flag that indicates whether or not the tick labels are
* 'vertical'.
*
* @return The height of the tallest tick label.
*/
protected int findMaximumTickLabelHeight(final List ticks, final GC g2,
final Rectangle drawArea, final boolean vertical) {
final RectangleInsets insets = this.getTickLabelInsets();
final Font font = this.getTickLabelFont();
int maxHeight = 0;
g2.setFont(font);
final FontMetrics fm = g2.getFontMetrics();
if (vertical) {
for (final Iterator iter = ticks.iterator(); iter.hasNext();) {
final ITick tick = (ITick) iter.next();
final Point extent = g2.textExtent(tick.getText());
maxHeight = Math.max(maxHeight, extent.x + insets.getTop()
+ insets.getBottom());
}
} else {
maxHeight = fm.getHeight() + insets.getTop() + insets.getBottom();
}
return maxHeight;
}
/**
* A utility method for determining the width of the widest tick label.
*
* @param ticks
* the ticks.
* @param g2
* the graphics device.
* @param drawArea
* the area within which the plot and axes should be drawn.
* @param vertical
* a flag that indicates whether or not the tick labels are
* 'vertical'.
*
* @return The width of the tallest tick label.
*/
protected int findMaximumTickLabelWidth(final List ticks, final GC g2,
final Rectangle drawArea, final boolean vertical) {
final RectangleInsets insets = this.getTickLabelInsets();
final Font font = this.getTickLabelFont();
int maxWidth = 0;
g2.setFont(font);
final FontMetrics fm = g2.getFontMetrics();
if (!vertical) {
for (final Iterator iter = ticks.iterator(); iter.hasNext();) {
final ITick tick = (ITick) iter.next();
final Point extent = g2.textExtent(tick.getText());
maxWidth = Math.max(maxWidth, extent.x + insets.getLeft()
+ insets.getRight());
}
} else {
maxWidth = fm.getHeight() + insets.getTop() + insets.getBottom();
}
return maxWidth;
}
/**
* Returns the minimum size allowed for the axis range when it is
* automatically calculated.
*
* @return The minimum range.
*
* @see #setAutoRangeMinimumSize(double)
*/
public double getAutoRangeMinimumSize() {
return this.autoRangeMinimumSize;
}
/**
* Returns the auto tick index.
*
* @return The auto tick index.
*
* @see #setAutoTickIndex(int)
*/
protected int getAutoTickIndex() {
return this.autoTickIndex;
}
/**
* Returns the fixed auto range.
*
* @return The length.
*
* @see #setFixedAutoRange(double)
*/
public double getFixedAutoRange() {
return this.fixedAutoRange;
}
/**
* Returns the lower bound of the axis range.
*
* @return The lower bound.
*
* @see #setLowerBound(double)
*/
public double getLowerBound() {
return this.range.getLowerBound();
}
/**
* Returns the lower margin for the axis, expressed as a percentage of the
* axis range. This controls the space added to the lower end of the axis
* when the axis range is automatically calculated (it is ignored when the
* axis range is set explicitly). The default value is 0.05 (five percent).
*
* @return The lower margin.
*
* @see #setLowerMargin(double)
*/
public double getLowerMargin() {
return this.lowerMargin;
}
/**
* Returns the range for the axis.
*
* @return The axis range (never <code>null</code>).
*
* @see #setRange(Range)
*/
public Range getRange() {
return this.range;
}
/**
* Returns the source for obtaining standard tick units for the axis.
*
* @return The source (possibly <code>null</code>).
*
* @see #setStandardTickUnits(TickUnitSource)
*/
public TickUnitSource getStandardTickUnits() {
return this.standardTickUnits;
}
/**
* Returns the upper bound for the axis range.
*
* @return The upper bound.
*
* @see #setUpperBound(double)
*/
public double getUpperBound() {
return this.range.getUpperBound();
}
/**
* Returns the upper margin for the axis, expressed as a percentage of the
* axis range. This controls the space added to the lower end of the axis
* when the axis range is automatically calculated (it is ignored when the
* axis range is set explicitly). The default value is 0.05 (five percent).
*
* @return The upper margin.
*
* @see #setUpperMargin(double)
*/
public double getUpperMargin() {
return this.upperMargin;
}
/**
* Returns the flag that controls whether or not the axis range is
* automatically adjusted to fit the data values.
*
* @return The flag.
*
* @see #setAutoRange(boolean)
*/
public boolean isAutoRange() {
return this.autoRange;
}
/**
* Returns a flag indicating whether or not the tick unit is automatically
* selected from a range of standard tick units.
*
* @return A flag indicating whether or not the tick unit is automatically
* selected.
*
* @see #setAutoTickUnitSelection(boolean)
*/
public boolean isAutoTickUnitSelection() {
return this.autoTickUnitSelection;
}
/**
* Returns a flag that controls the direction of values on the axis.
* <P>
* For a regular axis, values increase from left to right (for a horizontal
* axis) and bottom to top (for a vertical axis). When the axis is
* 'inverted', the values increase in the opposite direction.
*
* @return The flag.
*
* @see #setInverted(boolean)
*/
public boolean isInverted() {
return this.inverted;
}
/**
* Returns a flag that controls whether or not the axis line has an arrow
* drawn that points in the negative direction for the axis.
*
* @return A boolean.
*
* @see #setNegativeArrowVisible(boolean)
*/
public boolean isNegativeArrowVisible() {
return this.negativeArrowVisible;
}
/**
* Returns a flag that controls whether or not the axis line has an arrow
* drawn that points in the positive direction for the axis.
*
* @return A boolean.
*
* @see #setPositiveArrowVisible(boolean)
*/
public boolean isPositiveArrowVisible() {
return this.positiveArrowVisible;
}
/**
* Returns <code>true</code> if the tick labels should be rotated (to
* vertical), and <code>false</code> otherwise.
*
* @return <code>true</code> or <code>false</code>.
*
* @see #setVerticalTickLabels(boolean)
*/
public boolean isVerticalTickLabels() {
return this.verticalTickLabels;
}
/**
* Converts a coordinate in Java2D space to the corresponding data value,
* assuming that the axis runs along one edge of the specified dataArea.
*
* @param java2DValue
* the coordinate in Java2D space.
* @param area
* the area in which the data is plotted.
* @param edge
* the edge along which the axis lies.
*
* @return The data value.
*
* @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
*/
public abstract double java2DToValue(double java2DValue, Rectangle area,
RectangleEdge edge);
/**
* Converts a length in data coordinates into the corresponding length in
* Java2D coordinates.
*
* @param length
* the length.
* @param area
* the plot area.
* @param edge
* the edge along which the axis lies.
*
* @return The length in Java2D coordinates.
*/
public double lengthToJava2D(final double length, final Rectangle area,
final RectangleEdge edge) {
final double zero = this.valueToJava2D(0.0, area, edge);
final double l = this.valueToJava2D(length, area, edge);
return Math.abs(l - zero);
}
/**
* Returns the space required to draw the axis.
*
* @param g2
* the graphics device.
* @param plot
* the plot that the axis belongs to.
* @param plotArea
* the area within which the plot should be drawn.
* @param edge
* the axis location.
* @param space
* the space already reserved (for other axes).
*
* @return The space required to draw the axis (including pre-reserved
* space).
*/
public AxisSpace reserveSpace(final GC g2, final Plot plot,
final Rectangle plotArea, final RectangleEdge edge, AxisSpace space) {
// create a new space object if one wasn't supplied...
if (space == null) {
space = new AxisSpace();
}
// if the axis is not visible, no additional space is required...
if (!this.isVisible()) {
return space;
}
// if the axis has a fixed dimension, return it...
final int dimension = this.getFixedDimension();
if (dimension > 0.0) {
space.ensureAtLeast(dimension, edge);
}
// calculate the max size of the tick labels (if visible)...
int tickLabelHeight = 0;
int tickLabelWidth = 0;
if (this.isTickLabelsVisible()) {
g2.setFont(this.getTickLabelFont());
final List ticks = this.refreshTicks(g2, new AxisState(), plotArea,
edge);
if (edge.isTopOrBottom()) {
tickLabelHeight = this.findMaximumTickLabelHeight(ticks, g2,
plotArea, this.isVerticalTickLabels());
} else if (edge.isLeftOrRight()) {
tickLabelWidth = this.findMaximumTickLabelWidth(ticks, g2,
plotArea, this.isVerticalTickLabels());
}
}
// get the axis label size and update the space object...
final Rectangle labelEnclosure = this.getLabelEnclosure(g2, edge);
int labelHeight = 0;
int labelWidth = 0;
if (edge.isTopOrBottom()) {
labelHeight = labelEnclosure.height;
space.add(labelHeight + tickLabelHeight, edge);
} else if (edge.isLeftOrRight()) {
labelWidth = labelEnclosure.width;
space.add(labelWidth + tickLabelWidth, edge);
}
return space;
}
/**
* Increases or decreases the axis range by the specified percentage about
* the central value and sends an {@link AxisChangeEvent} to all registered
* listeners.
* <P>
* To double the length of the axis range, use 200% (2.0). To halve the
* length of the axis range, use 50% (0.5).
*
* @param percent
* the resize factor.
*/
public void resizeRange(final double percent) {
this.resizeRange(percent, this.range.getCentralValue());
}
/**
* Increases or decreases the axis range by the specified percentage about
* the specified anchor value and sends an {@link AxisChangeEvent} to all
* registered listeners.
* <P>
* To double the length of the axis range, use 200% (2.0). To halve the
* length of the axis range, use 50% (0.5).
*
* @param percent
* the resize factor.
* @param anchorValue
* the new central value after the resize.
*/
public void resizeRange(final double percent, final double anchorValue) {
if (percent > 0.0) {
final double halfLength = this.range.getLength() * percent / 2;
final Range adjusted = new Range(anchorValue - halfLength,
anchorValue + halfLength);
this.setRange(adjusted);
} else {
this.setAutoRange(true);
}
}
/**
* Sets a flag that determines whether or not the axis range is
* automatically adjusted to fit the data, and notifies registered listeners
* that the axis has been modified.
*
* @param auto
* the new value of the flag.
*
* @see #isAutoRange()
*/
public void setAutoRange(final boolean auto) {
this.setAutoRange(auto, true);
}
/**
* Sets the auto range attribute. If the <code>notify</code> flag is set, an
* {@link AxisChangeEvent} is sent to registered listeners.
*
* @param auto
* the flag.
* @param notify
* notify listeners?
*
* @see #isAutoRange()
*/
protected void setAutoRange(final boolean auto, final boolean notify) {
if (this.autoRange != auto) {
this.autoRange = auto;
if (this.autoRange) {
this.autoAdjustRange();
}
if (notify) {
this.notifyListeners(new AxisChangeEvent(this));
}
}
}
/**
* Sets the auto range minimum size and sends an {@link AxisChangeEvent} to
* all registered listeners.
*
* @param size
* the size.
*
* @see #getAutoRangeMinimumSize()
*/
public void setAutoRangeMinimumSize(final double size) {
this.setAutoRangeMinimumSize(size, true);
}
/**
* Sets the minimum size allowed for the axis range when it is automatically
* calculated.
* <p>
* If requested, an {@link AxisChangeEvent} is forwarded to all registered
* listeners.
*
* @param size
* the new minimum.
* @param notify
* notify listeners?
*/
public void setAutoRangeMinimumSize(final double size, final boolean notify) {
if (size <= 0.0) {
throw new IllegalArgumentException(
"NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
}
if (this.autoRangeMinimumSize != size) {
this.autoRangeMinimumSize = size;
if (this.autoRange) {
this.autoAdjustRange();
}
if (notify) {
this.notifyListeners(new AxisChangeEvent(this));
}
}
}
/**
* Sets the auto tick index.
*
* @param index
* the new value.
*
* @see #getAutoTickIndex()
*/
protected void setAutoTickIndex(final int index) {
this.autoTickIndex = index;
}
/**
* Sets a flag indicating whether or not the tick unit is automatically
* selected from a range of standard tick units. If the flag is changed,
* registered listeners are notified that the chart has changed.
*
* @param flag
* the new value of the flag.
*
* @see #isAutoTickUnitSelection()
*/
public void setAutoTickUnitSelection(final boolean flag) {
this.setAutoTickUnitSelection(flag, true);
}
/**
* Sets a flag indicating whether or not the tick unit is automatically
* selected from a range of standard tick units.
*
* @param flag
* the new value of the flag.
* @param notify
* notify listeners?
*
* @see #isAutoTickUnitSelection()
*/
public void setAutoTickUnitSelection(final boolean flag,
final boolean notify) {
if (this.autoTickUnitSelection != flag) {
this.autoTickUnitSelection = flag;
if (notify) {
this.notifyListeners(new AxisChangeEvent(this));
}
}
}
/**
* Sets the fixed auto range for the axis.
*
* @param length
* the range length.
*
* @see #getFixedAutoRange()
*/
public void setFixedAutoRange(final double length) {
this.fixedAutoRange = length;
if (this.isAutoRange()) {
this.autoAdjustRange();
}
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets a flag that controls the direction of values on the axis, and
* notifies registered listeners that the axis has changed.
*
* @param flag
* the flag.
*
* @see #isInverted()
*/
public void setInverted(final boolean flag) {
if (this.inverted != flag) {
this.inverted = flag;
this.notifyListeners(new AxisChangeEvent(this));
}
}
/**
* Sets the lower bound for the axis range. An {@link AxisChangeEvent} is
* sent to all registered listeners.
*
* @param min
* the new minimum.
*
* @see #getLowerBound()
*/
public void setLowerBound(final double min) {
if (this.range.getUpperBound() > min) {
this.setRange(new Range(min, this.range.getUpperBound()));
} else {
this.setRange(new Range(min, min + 1.0));
}
}
/**
* Sets the lower margin for the axis (as a percentage of the axis range)
* and sends an {@link AxisChangeEvent} to all registered listeners. This
* margin is added only when the axis range is auto-calculated - if you set
* the axis range manually, the margin is ignored.
*
* @param margin
* the margin percentage (for example, 0.05 is five percent).
*
* @see #setUpperMargin(double)
*/
public void setLowerMargin(final double margin) {
this.lowerMargin = margin;
if (this.isAutoRange()) {
this.autoAdjustRange();
}
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets a flag that controls whether or not the axis lines has an arrow
* drawn that points in the negative direction for the axis, and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param visible
* the flag.
*
* @see #setNegativeArrowVisible(boolean)
*/
public void setNegativeArrowVisible(final boolean visible) {
this.negativeArrowVisible = visible;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets a flag that controls whether or not the axis lines has an arrow
* drawn that points in the positive direction for the axis, and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param visible
* the flag.
*
* @see #isPositiveArrowVisible()
*/
public void setPositiveArrowVisible(final boolean visible) {
this.positiveArrowVisible = visible;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the axis range and sends an {@link AxisChangeEvent} to all
* registered listeners. As a side-effect, the auto-range flag is set to
* <code>false</code>.
*
* @param lower
* the lower axis limit.
* @param upper
* the upper axis limit.
*
* @see #getRange()
*/
public void setRange(final double lower, final double upper) {
this.setRange(new Range(lower, upper));
}
/**
* Sets the range attribute and sends an {@link AxisChangeEvent} to all
* registered listeners. As a side-effect, the auto-range flag is set to
* <code>false</code>.
*
* @param range
* the range (<code>null</code> not permitted).
*
* @see #getRange()
*/
public void setRange(final Range range) {
// defer argument checking
this.setRange(range, true, true);
}
/**
* Sets the range for the axis, if requested, sends an
* {@link AxisChangeEvent} to all registered listeners. As a side-effect,
* the auto-range flag is set to <code>false</code> (optional).
*
* @param range
* the range (<code>null</code> not permitted).
* @param turnOffAutoRange
* a flag that controls whether or not the auto range is turned
* off.
* @param notify
* a flag that controls whether or not listeners are notified.
*
* @see #getRange()
*/
public void setRange(final Range range, final boolean turnOffAutoRange,
final boolean notify) {
if (range == null) {
throw new IllegalArgumentException("Null 'range' argument.");
}
if (turnOffAutoRange) {
this.autoRange = false;
}
this.range = range;
if (notify) {
this.notifyListeners(new AxisChangeEvent(this));
}
}
/**
* Sets the axis range, where the new range is 'size' in length, and
* centered on 'value'.
*
* @param value
* the central value.
* @param length
* the range length.
*/
public void setRangeAboutValue(final double value, final double length) {
this.setRange(new Range(value - length / 2, value + length / 2));
}
/**
* Sets the axis range (after first adding the current margins to the range)
* and sends an {@link AxisChangeEvent} to all registered listeners. As a
* side-effect, the auto-range flag is set to <code>false</code>.
*
* @param lower
* the lower axis limit.
* @param upper
* the upper axis limit.
*/
public void setRangeWithMargins(final double lower, final double upper) {
this.setRangeWithMargins(new Range(lower, upper));
}
/**
* Sets the range for the axis (after first adding the current margins to
* the specified range) and sends an {@link AxisChangeEvent} to all
* registered listeners.
*
* @param range
* the range (<code>null</code> not permitted).
*/
public void setRangeWithMargins(final Range range) {
this.setRangeWithMargins(range, true, true);
}
/**
* Sets the range for the axis after first adding the current margins to the
* range and, if requested, sends an {@link AxisChangeEvent} to all
* registered listeners. As a side-effect, the auto-range flag is set to
* <code>false</code> (optional).
*
* @param range
* the range (excluding margins, <code>null</code> not
* permitted).
* @param turnOffAutoRange
* a flag that controls whether or not the auto range is turned
* off.
* @param notify
* a flag that controls whether or not listeners are notified.
*/
public void setRangeWithMargins(final Range range,
final boolean turnOffAutoRange, final boolean notify) {
if (range == null) {
throw new IllegalArgumentException("Null 'range' argument.");
}
this.setRange(Range.expand(range, this.getLowerMargin(), this
.getUpperMargin()), turnOffAutoRange, notify);
}
/**
* Sets the source for obtaining standard tick units for the axis and sends
* an {@link AxisChangeEvent} to all registered listeners. The axis will try
* to select the smallest tick unit from the source that does not cause the
* tick labels to overlap (see also the
* {@link #setAutoTickUnitSelection(boolean)} method.
*
* @param source
* the source for standard tick units (<code>null</code>
* permitted).
*
* @see #getStandardTickUnits()
*/
public void setStandardTickUnits(final TickUnitSource source) {
this.standardTickUnits = source;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the upper bound for the axis range. An {@link AxisChangeEvent} is
* sent to all registered listeners.
*
* @param max
* the new maximum.
*
* @see #getUpperBound()
*/
public void setUpperBound(final double max) {
if (this.range.getLowerBound() < max) {
this.setRange(new Range(this.range.getLowerBound(), max));
} else {
this.setRange(max - 1.0, max);
}
}
/**
* Sets the upper margin for the axis (as a percentage of the axis range)
* and sends an {@link AxisChangeEvent} to all registered listeners. This
* margin is added only when the axis range is auto-calculated - if you set
* the axis range manually, the margin is ignored.
*
* @param margin
* the margin percentage (for example, 0.05 is five percent).
*
* @see #setLowerMargin(double)
*/
public void setUpperMargin(final double margin) {
this.upperMargin = margin;
if (this.isAutoRange()) {
this.autoAdjustRange();
}
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the flag that controls whether the tick labels are displayed
* vertically (that is, rotated 90 degrees from horizontal). If the flag is
* changed, an {@link AxisChangeEvent} is sent to all registered listeners.
*
* @param flag
* the flag.
*
* @see #isVerticalTickLabels()
*/
public void setVerticalTickLabels(final boolean flag) {
if (this.verticalTickLabels != flag) {
this.verticalTickLabels = flag;
this.notifyListeners(new AxisChangeEvent(this));
}
}
/**
* Converts a data value to a coordinate in Java2D space, assuming that the
* axis runs along one edge of the specified dataArea.
* <p>
* Note that it is possible for the coordinate to fall outside the area.
*
* @param value
* the data value.
* @param area
* the area for plotting the data.
* @param edge
* the edge along which the axis lies.
*
* @return The Java2D coordinate.
*
* @see #java2DToValue(double, Rectangle2D, RectangleEdge)
*/
public abstract double valueToJava2D(double value, Rectangle area,
RectangleEdge edge);
/**
* Zooms in on the current range.
*
* @param lowerPercent
* the new lower bound.
* @param upperPercent
* the new upper bound.
*/
public void zoomRange(final double lowerPercent, final double upperPercent) {
final double start = this.range.getLowerBound();
final double length = this.range.getLength();
Range adjusted = null;
if (this.isInverted()) {
adjusted = new Range(start + (length * (1 - upperPercent)), start
+ (length * (1 - lowerPercent)));
} else {
adjusted = new Range(start + length * lowerPercent, start + length
* upperPercent);
}
this.setRange(adjusted);
}
}