package com.positive.charts.axis;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import com.positive.charts.axis.ticks.Tick;
import com.positive.charts.block.RectangleInsets;
import com.positive.charts.common.RectangleEdge;
import com.positive.charts.data.util.ObjectUtilities;
import com.positive.charts.data.util.RectangleAnchor;
import com.positive.charts.entity.CategoryLabelEntity;
import com.positive.charts.entity.EntityCollection;
import com.positive.charts.event.AxisChangeEvent;
import com.positive.charts.plot.CategoryPlot;
import com.positive.charts.plot.Plot;
import com.positive.charts.plot.PlotRenderingInfo;
import com.positive.charts.util.CategoryLabelWidthType;
import com.positive.charts.util.G2TextMeasurer;
import com.positive.charts.util.RectangleUtil;
import com.positive.charts.util.Size2D;
import com.positive.charts.util.TextBlock;
import com.positive.charts.util.TextUtilities;
/**
* An axis that displays categories.
*/
public class CategoryAxis extends BaseAxis {
/** For serialization. */
private static final long serialVersionUID = 5886554608114265863L;
/**
* The default margin for the axis (used for both lower and upper margins).
*/
public static final double DEFAULT_AXIS_MARGIN = 0.05;
/**
* The default margin between categories (a percentage of the overall axis
* length).
*/
public static final double DEFAULT_CATEGORY_MARGIN = 0.20;
/** The amount of space reserved at the start of the axis. */
private double lowerMargin;
/** The amount of space reserved at the end of the axis. */
private double upperMargin;
/** The amount of space reserved between categories. */
private double categoryMargin;
/** The maximum number of lines for category labels. */
private int maximumCategoryLabelLines;
/**
* A ratio that is multiplied by the width of one category to determine the
* maximum label width.
*/
private float maximumCategoryLabelWidthRatio;
/** The category label offset. */
private int categoryLabelPositionOffset;
/**
* A structure defining the category label positions for each axis location.
*/
private CategoryLabelPositions categoryLabelPositions;
/** Storage for tick label font overrides (if any). */
private Map tickLabelFontMap;
/** Storage for tick label paint overrides (if any). */
private transient Map tickLabelPaintMap;
/** Storage for the category label tooltips (if any). */
private Map categoryLabelToolTips;
/**
* Creates a new category axis with no label.
*/
public CategoryAxis() {
this(null);
}
/**
* Constructs a category axis, using default values where necessary.
*
* @param label
* the axis label (<code>null</code> permitted).
*/
public CategoryAxis(final String label) {
super(label);
this.lowerMargin = DEFAULT_AXIS_MARGIN;
this.upperMargin = DEFAULT_AXIS_MARGIN;
this.categoryMargin = DEFAULT_CATEGORY_MARGIN;
this.maximumCategoryLabelLines = 1;
this.maximumCategoryLabelWidthRatio = 0.0f;
// setTickMarksVisible(false); // not supported by this axis type yet
this.categoryLabelPositionOffset = 4;
this.categoryLabelPositions = CategoryLabelPositions.STANDARD;
this.tickLabelFontMap = new HashMap();
this.tickLabelPaintMap = new HashMap();
this.categoryLabelToolTips = new HashMap();
}
/**
* Adds a tooltip to the specified category and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param category
* the category (<code>null<code> not permitted).
* @param tooltip
* the tooltip text (<code>null</code> permitted).
*/
public void addCategoryLabelToolTip(final Comparable category,
final String tooltip) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.categoryLabelToolTips.put(category, tooltip);
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Calculates the size (width or height, depending on the location of the
* axis) of a category gap.
*
* @param categoryCount
* the number of categories.
* @param area
* the area within which the categories will be drawn.
* @param edge
* the axis location.
*
* @return The category gap width.
*/
protected double calculateCategoryGapSize(final int categoryCount,
final Rectangle area, final RectangleEdge edge) {
double result = 0.0;
double available = 0.0;
if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
available = area.width;
} else if ((edge == RectangleEdge.LEFT)
|| (edge == RectangleEdge.RIGHT)) {
available = area.height;
}
if (categoryCount > 1) {
result = available * this.getCategoryMargin() / (categoryCount - 1);
}
return result;
}
/**
* Calculates the size (width or height, depending on the location of the
* axis) of a category.
*
* @param categoryCount
* the number of categories.
* @param area
* the area within which the categories will be drawn.
* @param edge
* the axis location.
*
* @return The category size.
*/
protected double calculateCategorySize(final int categoryCount,
final Rectangle area, final RectangleEdge edge) {
double result = 0.0;
double available = 0.0;
if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
available = area.width;
} else if ((edge == RectangleEdge.LEFT)
|| (edge == RectangleEdge.RIGHT)) {
available = area.height;
}
if (categoryCount > 1) {
result = available
* (1 - this.getLowerMargin() - this.getUpperMargin() - this
.getCategoryMargin());
result = result / categoryCount;
} else {
result = available
* (1 - this.getLowerMargin() - this.getUpperMargin());
}
return result;
}
/**
* A utility method for determining the height of a text block.
*
* @param block
* the text block.
* @param position
* the label position.
* @param g2
* the graphics device.
*
* @return The height.
*/
protected double calculateTextBlockHeight(final TextBlock block,
final CategoryLabelPosition position, final GC g2) {
final RectangleInsets insets = this.getTickLabelInsets();
final Size2D size = block.calculateDimensions(g2);
final Rectangle box = RectangleUtil.Double(0.0, 0.0, size.width,
size.height);
// Shape rotatedBox = ShapeUtilities.rotateShape(
// box, position.getAngle(), 0.0f, 0.0f
// );
final Rectangle rotatedBox = box;
final double h = rotatedBox.height + insets.getTop()
+ insets.getBottom();
return h;
}
/**
* A utility method for determining the width of a text block.
*
* @param block
* the text block.
* @param position
* the position.
* @param g2
* the graphics device.
*
* @return The width.
*/
protected double calculateTextBlockWidth(final TextBlock block,
final CategoryLabelPosition position, final GC g2) {
final RectangleInsets insets = this.getTickLabelInsets();
final Size2D size = block.calculateDimensions(g2);
final Rectangle box = RectangleUtil.Double(0.0, 0.0, size.width,
size.height);
// TODO : support rotation
// Shape rotatedBox = ShapeUtilities.rotateShape(
// box, position.getAngle(), 0.0f, 0.0f
// );
final Rectangle rotatedBox = box;
final double w = rotatedBox.width + insets.getTop()
+ insets.getBottom();
return w;
}
/**
* Clears the category label tooltips and sends an {@link AxisChangeEvent}
* to all registered listeners.
*/
public void clearCategoryLabelToolTips() {
this.categoryLabelToolTips.clear();
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Creates a clone of the axis.
*
* @return A clone.
*
* @throws CloneNotSupportedException
* if some component of the axis does not support cloning.
*/
public Object clone() throws CloneNotSupportedException {
final CategoryAxis clone = (CategoryAxis) super.clone();
clone.tickLabelFontMap = new HashMap(this.tickLabelFontMap);
clone.tickLabelPaintMap = new HashMap(this.tickLabelPaintMap);
clone.categoryLabelToolTips = new HashMap(this.categoryLabelToolTips);
return clone;
}
/**
* Configures the axis against the current plot.
*/
public void configure() {
// nothing required
}
/**
* Creates a label.
*
* @param category
* the category.
* @param width
* the available width.
* @param edge
* the edge on which the axis appears.
* @param g2
* the graphics device.
*
* @return A label.
*/
protected TextBlock createLabel(final Comparable category,
final float width, final RectangleEdge edge, final GC g2) {
final TextBlock label = TextUtilities.createTextBlock(category
.toString(), this.getTickLabelFont(category), this
.getTickLabelPaint(category), width,
this.maximumCategoryLabelLines, new G2TextMeasurer(g2));
return label;
}
/**
* Draws the axis on a Java 2D graphics device (such as the screen or a
* printer).
*
* @param g2
* the graphics device (<code>null</code> not permitted).
* @param cursor
* the cursor location.
* @param plotArea
* the area within which the axis should be drawn (
* <code>null</code> not permitted).
* @param dataArea
* the area within which the plot is being drawn (
* <code>null</code> not permitted).
* @param edge
* the location of the axis (<code>null</code> not permitted).
* @param plotState
* collects information about the plot (<code>null</code>
* permitted).
*
* @return The axis state (never <code>null</code>).
*/
public AxisState draw(final GC g2, final double cursor,
final Rectangle plotArea, final Rectangle dataArea,
final RectangleEdge edge, final PlotRenderingInfo plotState) {
// if the axis is not visible, don't draw it...
if (!this.isVisible()) {
return new AxisState(cursor);
}
if (this.isAxisLineVisible()) {
this.drawAxisLine(g2, (int) (cursor + 0.5), dataArea, edge);
}
// draw the category labels and axis label
AxisState state = new AxisState(cursor);
state = this.drawCategoryLabels(g2, plotArea, dataArea, edge, state,
plotState);
state = this.drawLabel(this.getLabel(), g2, plotArea, dataArea, edge,
state);
return state;
}
/**
* Draws the category labels and returns the updated axis state.
*
* @param g2
* the graphics device (<code>null</code> not permitted).
* @param plotArea
* the plot area (<code>null</code> not permitted).
* @param dataArea
* the area inside the axes (<code>null</code> not permitted).
* @param edge
* the axis location (<code>null</code> not permitted).
* @param state
* the axis state (<code>null</code> not permitted).
* @param plotState
* collects information about the plot (<code>null</code>
* permitted).
*
* @return The updated axis state (never <code>null</code>).
*/
protected AxisState drawCategoryLabels(final GC g2,
final Rectangle plotArea, final Rectangle dataArea,
final RectangleEdge edge, final AxisState state,
final PlotRenderingInfo plotState) {
if (state == null) {
throw new IllegalArgumentException("Null 'state' argument.");
}
if (this.isTickLabelsVisible()) {
final List ticks = this.refreshTicks(g2, state, plotArea, edge);
state.setTicks(ticks);
int categoryIndex = 0;
final Iterator iterator = ticks.iterator();
while (iterator.hasNext()) {
final CategoryTick tick = (CategoryTick) iterator.next();
g2.setFont(this.getTickLabelFont(tick.getCategory()));
g2.setForeground(this.getTickLabelPaint(tick.getCategory()));
final CategoryLabelPosition position = this.categoryLabelPositions
.getLabelPosition(edge);
double x0 = 0.0;
double x1 = 0.0;
double y0 = 0.0;
double y1 = 0.0;
if (edge == RectangleEdge.TOP) {
x0 = this.getCategoryStart(categoryIndex, ticks.size(),
dataArea, edge);
x1 = this.getCategoryEnd(categoryIndex, ticks.size(),
dataArea, edge);
y1 = state.getCursor() - this.categoryLabelPositionOffset;
y0 = y1 - state.getMax();
} else if (edge == RectangleEdge.BOTTOM) {
x0 = this.getCategoryStart(categoryIndex, ticks.size(),
dataArea, edge);
x1 = this.getCategoryEnd(categoryIndex, ticks.size(),
dataArea, edge);
y0 = state.getCursor() + this.categoryLabelPositionOffset;
y1 = y0 + state.getMax();
} else if (edge == RectangleEdge.LEFT) {
y0 = this.getCategoryStart(categoryIndex, ticks.size(),
dataArea, edge);
y1 = this.getCategoryEnd(categoryIndex, ticks.size(),
dataArea, edge);
x1 = state.getCursor() - this.categoryLabelPositionOffset;
x0 = x1 - state.getMax();
} else if (edge == RectangleEdge.RIGHT) {
y0 = this.getCategoryStart(categoryIndex, ticks.size(),
dataArea, edge);
y1 = this.getCategoryEnd(categoryIndex, ticks.size(),
dataArea, edge);
x0 = state.getCursor() + this.categoryLabelPositionOffset;
x1 = x0 - state.getMax();
}
final Rectangle area = RectangleUtil.Double(x0, y0, (x1 - x0),
(y1 - y0));
final Point anchorPoint = RectangleAnchor.coordinates(area,
position.getCategoryAnchor());
final TextBlock block = tick.getLabel();
block.draw(g2, anchorPoint.x, anchorPoint.y, position
.getLabelAnchor(), 0, 0, position.getAngle());
final Rectangle bounds = block.calculateBounds(g2,
anchorPoint.x, anchorPoint.y,
position.getLabelAnchor(), anchorPoint.x,
anchorPoint.y, position.getAngle());
if ((plotState != null) && (plotState.getOwner() != null)) {
final EntityCollection entities = plotState.getOwner()
.getEntityCollection();
if (entities != null) {
final String tooltip = this
.getCategoryLabelToolTip(tick.getCategory());
final Region boundRegion = new Region();
boundRegion.add(bounds);
entities
.add(new CategoryLabelEntity(
tick.getCategory(), boundRegion,
tooltip, null));
}
}
categoryIndex++;
}
if (edge.equals(RectangleEdge.TOP)) {
final double h = state.getMax();
state.cursorUp(h);
} else if (edge.equals(RectangleEdge.BOTTOM)) {
final double h = state.getMax();
state.cursorDown(h);
} else if (edge == RectangleEdge.LEFT) {
final double w = state.getMax();
state.cursorLeft(w);
} else if (edge == RectangleEdge.RIGHT) {
final double w = state.getMax();
state.cursorRight(w);
}
}
return state;
}
/**
* Draws the category labels and returns the updated axis state.
*
* @param g2
* the graphics device (<code>null</code> not permitted).
* @param dataArea
* the area inside the axes (<code>null</code> not permitted).
* @param edge
* the axis location (<code>null</code> not permitted).
* @param state
* the axis state (<code>null</code> not permitted).
* @param plotState
* collects information about the plot (<code>null</code>
* permitted).
*
* @return The updated axis state (never <code>null</code>).
*
* @deprecated Use
* {@link #drawCategoryLabels(Graphics2D, Rectangle2D, Rectangle2D, RectangleEdge, AxisState, PlotRenderingInfo)}
* .
*/
protected AxisState drawCategoryLabels(final GC g2,
final Rectangle dataArea, final RectangleEdge edge,
final AxisState state, final PlotRenderingInfo plotState) {
// this method is deprecated because we really need the plotArea
// when drawing the labels - see bug 1277726
return this.drawCategoryLabels(g2, dataArea, dataArea, edge, state,
plotState);
}
/**
* Tests this axis for equality with an arbitrary object.
*
* @param obj
* the object (<code>null</code> permitted).
*
* @return A boolean.
*/
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CategoryAxis)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
final CategoryAxis that = (CategoryAxis) obj;
if (that.lowerMargin != this.lowerMargin) {
return false;
}
if (that.upperMargin != this.upperMargin) {
return false;
}
if (that.categoryMargin != this.categoryMargin) {
return false;
}
if (that.maximumCategoryLabelWidthRatio != this.maximumCategoryLabelWidthRatio) {
return false;
}
if (that.categoryLabelPositionOffset != this.categoryLabelPositionOffset) {
return false;
}
if (!ObjectUtilities.equal(that.categoryLabelPositions,
this.categoryLabelPositions)) {
return false;
}
if (!ObjectUtilities.equal(that.categoryLabelToolTips,
this.categoryLabelToolTips)) {
return false;
}
if (!ObjectUtilities
.equal(this.tickLabelFontMap, that.tickLabelFontMap)) {
return false;
}
// TODO : SERIALIZATION : Fix below
// if (!equalPaintMaps(this.tickLabelPaintMap, that.tickLabelPaintMap))
// {
// return false;
// }
return true;
}
/**
* Returns the end coordinate for the specified category.
*
* @param category
* the category.
* @param categoryCount
* the number of categories.
* @param area
* the data area.
* @param edge
* the axis location.
*
* @return The coordinate.
*/
public double getCategoryEnd(final int category, final int categoryCount,
final Rectangle area, final RectangleEdge edge) {
return this.getCategoryStart(category, categoryCount, area, edge)
+ this.calculateCategorySize(categoryCount, area, edge);
}
// TODO: Change method name
/**
*
* Returns the Java 2D coordinate for a category.
*
* @param anchor
* the anchor point.
* @param category
* the category index.
* @param categoryCount
* the category count.
* @param area
* the data area.
* @param edge
* the location of the axis.
*
* @return The coordinate.
*/
public double getCategoryJava2DCoordinate(final CategoryAnchor anchor,
final int category, final int categoryCount, final Rectangle area,
final RectangleEdge edge) {
double result = 0.0;
if (anchor == CategoryAnchor.START) {
result = this.getCategoryStart(category, categoryCount, area, edge);
} else if (anchor == CategoryAnchor.MIDDLE) {
result = this
.getCategoryMiddle(category, categoryCount, area, edge);
} else if (anchor == CategoryAnchor.END) {
result = this.getCategoryEnd(category, categoryCount, area, edge);
}
return result;
}
/**
* Returns the offset between the axis and the category labels (before label
* positioning is taken into account).
*
* @return The offset (in Java2D units).
*/
public int getCategoryLabelPositionOffset() {
return this.categoryLabelPositionOffset;
}
/**
* Returns the category label position specification (this contains label
* positioning info for all four possible axis locations).
*
* @return The positions (never <code>null</code>).
*/
public CategoryLabelPositions getCategoryLabelPositions() {
return this.categoryLabelPositions;
}
/**
* Returns the tool tip text for the label belonging to the specified
* category.
*
* @param category
* the category (<code>null</code> not permitted).
*
* @return The tool tip text (possibly <code>null</code>).
*/
public String getCategoryLabelToolTip(final Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
return (String) this.categoryLabelToolTips.get(category);
}
/**
* Returns the category margin.
*
* @return The margin.
*/
public double getCategoryMargin() {
return this.categoryMargin;
}
/**
* Returns the middle coordinate for the specified category.
*
* @param category
* the category.
* @param categoryCount
* the number of categories.
* @param area
* the data area.
* @param edge
* the axis location.
*
* @return The coordinate.
*/
public double getCategoryMiddle(final int category,
final int categoryCount, final Rectangle area,
final RectangleEdge edge) {
return this.getCategoryStart(category, categoryCount, area, edge)
+ this.calculateCategorySize(categoryCount, area, edge) / 2;
}
/**
* Returns the starting coordinate for the specified category.
*
* @param category
* the category.
* @param categoryCount
* the number of categories.
* @param area
* the data area.
* @param edge
* the axis location.
*
* @return The coordinate.
*/
public double getCategoryStart(final int category, final int categoryCount,
final Rectangle area, final RectangleEdge edge) {
double result = 0.0;
if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
result = area.x + area.width * this.getLowerMargin();
} else if ((edge == RectangleEdge.LEFT)
|| (edge == RectangleEdge.RIGHT)) {
result = RectangleUtil.getMinY(area) + area.height
* this.getLowerMargin();
}
final double categorySize = this.calculateCategorySize(categoryCount,
area, edge);
final double categoryGapWidth = this.calculateCategoryGapSize(
categoryCount, area, edge);
result = result + category * (categorySize + categoryGapWidth);
return result;
}
/**
* Returns the lower margin for the axis.
*
* @return The margin.
*/
public double getLowerMargin() {
return this.lowerMargin;
}
/**
* Returns the maximum number of lines to use for each category label.
*
* @return The maximum number of lines.
*/
public int getMaximumCategoryLabelLines() {
return this.maximumCategoryLabelLines;
}
/**
* Returns the category label width ratio.
*
* @return The ratio.
*/
public float getMaximumCategoryLabelWidthRatio() {
return this.maximumCategoryLabelWidthRatio;
}
/**
* Returns the font for the tick label for the given category.
*
* @param category
* the category (<code>null</code> not permitted).
*
* @return The font (never <code>null</code>).
*/
public Font getTickLabelFont(final Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
Font result = (Font) this.tickLabelFontMap.get(category);
// if there is no specific font, use the general one...
if (result == null) {
result = this.getTickLabelFont();
}
if (result == null) {
}
return result;
}
/**
* Returns the paint for the tick label for the given category.
*
* @param category
* the category (<code>null</code> not permitted).
*
* @return The paint (never <code>null</code>).
*/
public Color getTickLabelPaint(final Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
Color result = (Color) this.tickLabelPaintMap.get(category);
// if there is no specific paint, use the general one...
if (result == null) {
result = this.getTickLabelPaint();
}
return result;
}
/**
* Returns the upper margin for the axis.
*
* @return The margin.
*/
public double getUpperMargin() {
return this.upperMargin;
}
/**
* Returns a hash code for this object.
*
* @return A hash code.
*/
public int hashCode() {
if (this.getLabel() != null) {
return this.getLabel().hashCode();
} else {
return 0;
}
}
/**
* Creates a temporary list of ticks that can be used when drawing the axis.
*
* @param g2
* the graphics device (used to get font measurements).
* @param state
* the axis state.
* @param dataArea
* the area inside the axes.
* @param edge
* the location of the axis.
*
* @return A list of ticks.
*/
public List refreshTicks(final GC g2, final AxisState state,
final Rectangle dataArea, final RectangleEdge edge) {
final List ticks = new java.util.ArrayList();
// sanity check for data area...
if ((dataArea.height <= 0.0) || (dataArea.width < 0.0)) {
return ticks;
}
final CategoryPlot plot = (CategoryPlot) this.getPlot();
final List categories = plot.getCategoriesForAxis(this);
double max = 0.0;
if (categories != null) {
final CategoryLabelPosition position = this.categoryLabelPositions
.getLabelPosition(edge);
float r = this.maximumCategoryLabelWidthRatio;
if (r <= 0.0) {
r = position.getWidthRatio();
}
float l = 0.0f;
if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) {
l = (float) this.calculateCategorySize(categories.size(),
dataArea, edge);
} else {
if (RectangleEdge.isLeftOrRight(edge)) {
l = dataArea.width;
} else {
l = dataArea.height;
}
}
int categoryIndex = 0;
final Iterator iterator = categories.iterator();
while (iterator.hasNext()) {
final Comparable category = (Comparable) iterator.next();
final TextBlock label = this.createLabel(category, l * r, edge,
g2);
if ((edge == RectangleEdge.TOP)
|| (edge == RectangleEdge.BOTTOM)) {
max = Math.max(max, this.calculateTextBlockHeight(label,
position, g2));
} else if ((edge == RectangleEdge.LEFT)
|| (edge == RectangleEdge.RIGHT)) {
max = Math.max(max, this.calculateTextBlockWidth(label,
position, g2));
}
final Tick tick = new CategoryTick(category, label, position
.getLabelAnchor(), position.getRotationAnchor(),
position.getAngle());
ticks.add(tick);
categoryIndex = categoryIndex + 1;
}
}
state.setMax(max);
return ticks;
}
/**
* Removes the tooltip for the specified category and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param category
* the category (<code>null<code> not permitted).
*/
public void removeCategoryLabelToolTip(final Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.categoryLabelToolTips.remove(category);
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Estimates the space required for the axis, given a specific drawing area.
*
* @param g2
* the graphics device (used to obtain font information).
* @param plot
* the plot that the axis belongs to.
* @param plotArea
* the area within which the axis should be drawn.
* @param edge
* the axis location (top or bottom).
* @param space
* the space already reserved.
*
* @return The space required to draw the axis.
*/
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;
}
// calculate the max size of the tick labels (if visible)...
double tickLabelHeight = 0.0;
double tickLabelWidth = 0.0;
if (this.isTickLabelsVisible()) {
g2.setFont(this.getTickLabelFont());
final AxisState state = new AxisState();
// we call refresh ticks just to get the maximum width or height
this.refreshTicks(g2, state, plotArea, edge);
if (edge == RectangleEdge.TOP) {
tickLabelHeight = state.getMax();
} else if (edge == RectangleEdge.BOTTOM) {
tickLabelHeight = state.getMax();
} else if (edge == RectangleEdge.LEFT) {
tickLabelWidth = state.getMax();
} else if (edge == RectangleEdge.RIGHT) {
tickLabelWidth = state.getMax();
}
}
// get the axis label size and update the space object...
final Rectangle labelEnclosure = this.getLabelEnclosure(g2, edge);
double labelHeight = 0.0;
double labelWidth = 0.0;
if (RectangleEdge.isTopOrBottom(edge)) {
labelHeight = labelEnclosure.height;
space.add((int) (labelHeight + tickLabelHeight
+ this.categoryLabelPositionOffset + 0.5), edge);
} else if (RectangleEdge.isLeftOrRight(edge)) {
labelWidth = labelEnclosure.width;
space.add((int) (labelWidth + tickLabelWidth
+ this.categoryLabelPositionOffset + 0.5), edge);
}
return space;
}
/**
* Sets the offset between the axis and the category labels (before label
* positioning is taken into account).
*
* @param offset
* the offset (in Java2D units).
*/
public void setCategoryLabelPositionOffset(final int offset) {
this.categoryLabelPositionOffset = offset;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the category label position specification for the axis and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param positions
* the positions (<code>null</code> not permitted).
*/
public void setCategoryLabelPositions(final CategoryLabelPositions positions) {
if (positions == null) {
throw new IllegalArgumentException("Null 'positions' argument.");
}
this.categoryLabelPositions = positions;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the category margin and sends an {@link AxisChangeEvent} to all
* registered listeners. The overall category margin is distributed over N-1
* gaps, where N is the number of categories on the axis.
*
* @param margin
* the margin as a percentage of the axis length (for example,
* 0.05 is five percent).
*/
public void setCategoryMargin(final double margin) {
this.categoryMargin = margin;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the lower margin for the axis and sends an {@link AxisChangeEvent}
* to all registered listeners.
*
* @param margin
* the margin as a percentage of the axis length (for example,
* 0.05 is five percent).
*/
public void setLowerMargin(final double margin) {
this.lowerMargin = margin;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the maximum number of lines to use for each category label and sends
* an {@link AxisChangeEvent} to all registered listeners.
*
* @param lines
* the maximum number of lines.
*/
public void setMaximumCategoryLabelLines(final int lines) {
this.maximumCategoryLabelLines = lines;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the maximum category label width ratio and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param ratio
* the ratio.
*/
public void setMaximumCategoryLabelWidthRatio(final float ratio) {
this.maximumCategoryLabelWidthRatio = ratio;
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the font for the tick label for the specified category and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param category
* the category (<code>null</code> not permitted).
* @param font
* the font (<code>null</code> permitted).
*/
public void setTickLabelFont(final Comparable category, final Font font) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
if (font == null) {
this.tickLabelFontMap.remove(category);
} else {
this.tickLabelFontMap.put(category, font);
}
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the paint for the tick label for the specified category and sends an
* {@link AxisChangeEvent} to all registered listeners.
*
* @param category
* the category (<code>null</code> not permitted).
* @param paint
* the paint (<code>null</code> permitted).
*/
public void setTickLabelPaint(final Comparable category, final Color paint) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
if (paint == null) {
this.tickLabelPaintMap.remove(category);
} else {
this.tickLabelPaintMap.put(category, paint);
}
this.notifyListeners(new AxisChangeEvent(this));
}
/**
* Sets the upper margin for the axis and sends an {@link AxisChangeEvent}
* to all registered listeners.
*
* @param margin
* the margin as a percentage of the axis length (for example,
* 0.05 is five percent).
*/
public void setUpperMargin(final double margin) {
this.upperMargin = margin;
this.notifyListeners(new AxisChangeEvent(this));
}
}