www.pudn.com > jfreechart-0.9.12.zip > CategoryAxis.java
/* ======================================
* JFreeChart : a free Java chart library
* ======================================
*
* Project Info: http://www.jfree.org/jfreechart/index.html
* Project Lead: David Gilbert (david.gilbert@object-refinery.com);
*
* (C) Copyright 2000-2003, by Object Refinery Limited and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* -----------------
* CategoryAxis.java
* -----------------
* (C) Copyright 2000-2003, by Object Refinery Limited.
*
* Original Author: David Gilbert;
* Contributor(s): -;
*
* $Id: CategoryAxis.java,v 1.14 2003/09/08 09:49:35 mungady Exp $
*
* Changes (from 21-Aug-2001)
* --------------------------
* 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
* 18-Sep-2001 : Updated header (DG);
* 04-Dec-2001 : Changed constructors to protected, and tidied up default values (DG);
* 19-Apr-2002 : Updated import statements (DG);
* 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
* 06-Nov-2002 : Moved margins from the CategoryPlot class (DG);
* 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
* 22-Jan-2002 : Removed monolithic constructor (DG);
* 26-Mar-2003 : Implemented Serializable (DG);
* 09-May-2003 : Merged HorizontalCategoryAxis and VerticalCategoryAxis into this class (DG);
* 13-Aug-2003 : Implemented Cloneable (DG);
*
*/
package org.jfree.chart.axis;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.LineMetrics;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jfree.chart.event.AxisChangeEvent;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RefineryUtilities;
/**
* The base class for axes that display categories.
*
* @author David Gilbert
*/
public class CategoryAxis extends Axis implements Cloneable, Serializable {
/** The default setting for rotated category labels. */
public static final boolean DEFAULT_VERTICAL_CATEGORY_LABELS = false;
/** 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;
/** A flag that indicates whether the category labels should be rotated 90 degrees. */
private boolean verticalCategoryLabels; // need to replace this with rotation, so that it fits
// with both horizontal and vertical axes
/** A flag that controls whether to skip category labels to avoid overlapping. */
private boolean skipCategoryLabelsToFit;
/** Tick line count */
private int maxTickLineCount;
/** Temporary list of categories. */
private List categories;
/** A temporary record of the amount of space reserved for the tick labels. */
protected double reservedForTickLabels;
/** A temporary record of the amount of space reserved for the axis label. */
protected double reservedForAxisLabel;
/**
* Constructs a category axis, using default values where necessary.
*
* @param label the axis label (null permitted).
*/
public CategoryAxis(String label) {
super(label);
this.lowerMargin = DEFAULT_AXIS_MARGIN;
this.upperMargin = DEFAULT_AXIS_MARGIN;
this.categoryMargin = DEFAULT_CATEGORY_MARGIN;
this.verticalCategoryLabels = DEFAULT_VERTICAL_CATEGORY_LABELS;
this.setTickMarksVisible(false); // not supported by this axis type yet
this.categories = new java.util.ArrayList();
this.reservedForAxisLabel = 0.0;
this.reservedForTickLabels = 0.0;
}
/**
* Returns the lower margin for the axis.
*
* @return the margin.
*/
public double getLowerMargin() {
return this.lowerMargin;
}
/**
* Sets the lower margin for the axis. An {@link AxisChangeEvent} is sent to all registered
* listeners.
*
* @param margin the new margin.
*/
public void setLowerMargin(double margin) {
this.lowerMargin = margin;
notifyListeners(new AxisChangeEvent(this));
}
/**
* Returns the upper margin for the axis.
*
* @return the margin.
*/
public double getUpperMargin() {
return this.upperMargin;
}
/**
* Sets the upper margin for the axis. An {@link AxisChangeEvent} is sent to all registered
* listeners.
*
* @param margin the new margin.
*/
public void setUpperMargin(double margin) {
this.upperMargin = margin;
notifyListeners(new AxisChangeEvent(this));
}
/**
* Returns the category margin.
*
* @return the margin.
*/
public double getCategoryMargin() {
return this.categoryMargin;
}
/**
* Sets the category margin. An {@link AxisChangeEvent} is sent to all registered
* listeners.
*
* @param margin the new margin.
*/
public void setCategoryMargin(double margin) {
this.categoryMargin = margin;
notifyListeners(new AxisChangeEvent(this));
}
/**
* Returns a flag indicating whether the category labels are rotated to vertical.
*
* @return The flag.
*/
public boolean isVerticalCategoryLabels() {
return this.verticalCategoryLabels;
}
/**
* Sets the flag that determines whether the category labels are rotated to vertical.
*
* @param flag the flag.
*/
public void setVerticalCategoryLabels(boolean flag) {
if (this.verticalCategoryLabels != flag) {
this.verticalCategoryLabels = flag;
notifyListeners(new AxisChangeEvent(this));
}
}
/**
* Returns the flag that determines whether the category labels are to be
* skipped to avoid overlapping.
*
* @return The flag.
*/
public boolean getSkipCategoryLabelsToFit() {
return this.skipCategoryLabelsToFit;
}
/**
* Sets the flag that determines whether the category labels are to be
* skipped to avoid overlapping.
*
* @param flag the new value of the flag.
*/
public void setSkipCategoryLabelsToFit(boolean flag) {
if (this.skipCategoryLabelsToFit != flag) {
this.skipCategoryLabelsToFit = flag;
notifyListeners(new AxisChangeEvent(this));
}
}
/**
* 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(CategoryAnchor anchor,
int category, int categoryCount, Rectangle2D area,
RectangleEdge edge) {
double result = 0.0;
if (anchor == CategoryAnchor.START) {
result = getCategoryStart(category, categoryCount, area, edge);
}
else if (anchor == CategoryAnchor.MIDDLE) {
result = getCategoryMiddle(category, categoryCount, area, edge);
}
else if (anchor == CategoryAnchor.END) {
result = getCategoryEnd(category, categoryCount, area, edge);
}
return result;
}
/**
* 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(int category, int categoryCount, Rectangle2D area,
RectangleEdge edge) {
double result = 0.0;
if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
result = area.getX() + area.getWidth() * getLowerMargin();
}
else if ((edge == RectangleEdge.LEFT) || (edge == RectangleEdge.RIGHT)) {
result = area.getMinY() + area.getHeight() * getLowerMargin();
}
double categorySize = calculateCategorySize(categoryCount, area, edge);
double categoryGapWidth = calculateCategoryGapSize(categoryCount, area, edge);
result = result + category * (categorySize + categoryGapWidth);
return result;
}
/**
* 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(int category, int categoryCount, Rectangle2D area,
RectangleEdge edge) {
return getCategoryStart(category, categoryCount, area, edge)
+ calculateCategorySize(categoryCount, area, edge) / 2;
}
/**
* 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(int category, int categoryCount, Rectangle2D area,
RectangleEdge edge) {
return getCategoryStart(category, categoryCount, area, edge)
+ calculateCategorySize(categoryCount, area, edge);
}
/**
* 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(int categoryCount, Rectangle2D area,
RectangleEdge edge) {
double result = 0.0;
double available = 0.0;
if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
available = area.getWidth();
}
else if ((edge == RectangleEdge.LEFT) || (edge == RectangleEdge.RIGHT)) {
available = area.getHeight();
}
if (categoryCount > 1) {
result = available * (1 - getLowerMargin() - getUpperMargin() - getCategoryMargin());
result = result / categoryCount;
}
else {
result = available * (1 - getLowerMargin() - getUpperMargin());
}
return result;
}
/**
* 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(int categoryCount, Rectangle2D area,
RectangleEdge edge) {
double result = 0.0;
double available = 0.0;
if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
available = area.getWidth();
}
else if ((edge == RectangleEdge.LEFT) || (edge == RectangleEdge.RIGHT)) {
available = area.getHeight();
}
if (categoryCount > 1) {
result = available * getCategoryMargin() / (categoryCount - 1);
}
return result;
}
/**
* Estimates the space required for the axis, given a specific drawing area, without any
* information about the space required for the range axis/axes.
*
* @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 estimated height required for the axis.
*/
public AxisSpace reserveSpace(Graphics2D g2, Plot plot, Rectangle2D plotArea,
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 (!isVisible()) {
return space;
}
// calculate the max size of the tick labels (if visible)...
double tickLabelHeight = 0.0;
double tickLabelWidth = 0.0;
if (isTickLabelsVisible()) {
g2.setFont(getTickLabelFont());
refreshTicks(g2, 0.0, plotArea, plotArea, edge);
Insets tickLabelInsets = getTickLabelInsets();
if (RectangleEdge.isTopOrBottom(edge)) {
tickLabelHeight = tickLabelInsets.top + tickLabelInsets.bottom;
tickLabelHeight += getMaxTickLabelHeight(g2, plotArea, isVerticalCategoryLabels());
this.reservedForTickLabels = tickLabelHeight;
}
else if (RectangleEdge.isLeftOrRight(edge)) {
tickLabelWidth = tickLabelInsets.left + tickLabelInsets.right;
tickLabelWidth += getMaxTickLabelWidth(g2, plotArea);
this.reservedForTickLabels = tickLabelWidth;
}
}
// get the axis label size and update the space object...
Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
double labelHeight = 0.0;
double labelWidth = 0.0;
if (RectangleEdge.isTopOrBottom(edge)) {
labelHeight = labelEnclosure.getHeight();
space.ensureAtLeast(labelHeight + tickLabelHeight, edge);
this.reservedForAxisLabel = labelHeight;
}
else if (RectangleEdge.isLeftOrRight(edge)) {
labelWidth = labelEnclosure.getWidth();
space.ensureAtLeast(labelWidth + tickLabelWidth, edge);
this.reservedForAxisLabel = labelWidth;
}
return space;
}
/**
* Configures the axis against the current plot. Nothing required in this class.
*/
public void configure() {
CategoryPlot plot = (CategoryPlot) this.getPlot();
this.categories = plot.getCategories();
}
/**
* Draws the axis on a Java 2D graphics device (such as the screen or a printer).
*
* @param g2 the graphics device.
* @param cursor the cursor location.
* @param plotArea the area within which the axis should be drawn.
* @param dataArea the area within which the plot is being drawn.
* @param edge the location of the axis.
*
* @return The new cursor location.
*/
public double draw(Graphics2D g2, double cursor,
Rectangle2D plotArea, Rectangle2D dataArea,
RectangleEdge edge) {
// if the axis is not visible, don't draw it...
if (!isVisible()) {
return 0.0;
}
// draw the category labels
double used1 = 0.0;
if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
used1 = drawHorizontalCategoryLabels(g2, cursor, plotArea, dataArea, edge);
}
else if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
used1 = drawVerticalCategoryLabels(g2, cursor, plotArea, dataArea, edge);
}
if (edge == RectangleEdge.TOP || edge == RectangleEdge.LEFT) {
cursor = cursor - used1;
}
else if (edge == RectangleEdge.BOTTOM || edge == RectangleEdge.RIGHT) {
cursor = cursor + used1;
}
// draw the axis label...
double used2 = drawLabel(getLabel(), g2, cursor, plotArea, dataArea, edge);
return used1 + used2;
}
/**
* Draws the category labels when the axis is 'horizontal'.
*
* @param g2 the graphics device.
* @param cursor the cursor location.
* @param plotArea the plot area.
* @param dataArea the area inside the axes.
* @param edge the axis location.
*
* @return The new cursor location.
*/
protected double drawHorizontalCategoryLabels(Graphics2D g2, double cursor,
Rectangle2D plotArea,
Rectangle2D dataArea,
RectangleEdge edge) {
if (isTickLabelsVisible()) {
Font tickLabelFont = getTickLabelFont();
g2.setFont(tickLabelFont);
g2.setPaint(getTickLabelPaint());
refreshTicks(g2, cursor, plotArea, dataArea, edge);
Insets tickLabelInsets = getTickLabelInsets();
double tickLabelHeight = tickLabelInsets.top + tickLabelInsets.bottom;
tickLabelHeight += getMaxTickLabelHeight(g2, plotArea, isVerticalCategoryLabels());
this.reservedForTickLabels = tickLabelHeight;
Iterator iterator = getTicks().iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (obj instanceof Tick) {
Tick tick = (Tick) obj;
if (isVerticalCategoryLabels()) {
RefineryUtilities.drawRotatedString(tick.getText(), g2,
tick.getX(), tick.getY(),
-Math.PI / 2);
}
else {
g2.drawString(tick.getText(), tick.getX(), tick.getY());
}
}
else {
Tick[] ts = (Tick[]) obj;
for (int i = 0; i < ts.length; i++) {
g2.drawString(ts[i].getText(), ts[i].getX(), ts[i].getY());
}
}
}
}
return this.reservedForTickLabels;
}
/**
* Draws the category labels when the axis is 'vertical'.
*
* @param g2 the graphics device.
* @param cursor the cursor location.
* @param plotArea the plot area.
* @param dataArea the area inside the axes.
* @param edge the axis location.
*
* @return The new cursor location.
*/
protected double drawVerticalCategoryLabels(Graphics2D g2, double cursor,
Rectangle2D plotArea,
Rectangle2D dataArea,
RectangleEdge edge) {
if (isTickLabelsVisible()) {
g2.setFont(getTickLabelFont());
g2.setPaint(getTickLabelPaint());
refreshTicks(g2, cursor, plotArea, dataArea, edge);
Iterator iterator = getTicks().iterator();
while (iterator.hasNext()) {
Tick tick = (Tick) iterator.next();
g2.drawString(tick.getText(), tick.getX(), tick.getY());
}
}
return this.reservedForTickLabels;
}
/**
* Returns true if the specified plot is compatible with the axis, and
* false otherwise.
*
* @param plot the plot.
*
* @return A boolean.
*/
protected boolean isCompatiblePlot(Plot plot) {
return (plot instanceof CategoryPlot);
}
/**
* 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 {
Object clone = super.clone();
return clone;
}
/**
* Tests this axis for equality with another object.
*
* @param obj the object.
*
* @return true or false.
*/
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof CategoryAxis) {
CategoryAxis axis = (CategoryAxis) obj;
if (super.equals(obj)) {
boolean b0 = (this.lowerMargin == axis.lowerMargin);
boolean b1 = (this.upperMargin == axis.upperMargin);
boolean b2 = (this.categoryMargin == axis.categoryMargin);
boolean b3 = (this.verticalCategoryLabels == axis.verticalCategoryLabels);
boolean b4 = (this.skipCategoryLabelsToFit == axis.skipCategoryLabelsToFit);
return b0 && b1 && b2 && b3 && b4;
}
}
return false;
}
/**
* 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 cursor the cursor location.
* @param plotArea the area where the plot and axes will be drawn.
* @param dataArea the area inside the axes.
* @param edge the location of the axis.
*/
public void refreshTicks(Graphics2D g2, double cursor,
Rectangle2D plotArea, Rectangle2D dataArea,
RectangleEdge edge) {
if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
refreshTicksHorizontal(g2, cursor, plotArea, dataArea, edge);
}
else if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
refreshTicksVertical(g2, cursor, plotArea, dataArea, edge);
}
}
/**
* 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 cursor the cursor location.
* @param drawArea the area where the plot and axes will be drawn.
* @param plotArea the area inside the axes.
* @param edge the location of the axis.
*/
public void refreshTicksVertical(Graphics2D g2, double cursor,
Rectangle2D drawArea, Rectangle2D plotArea,
RectangleEdge edge) {
getTicks().clear();
CategoryPlot plot = (CategoryPlot) getPlot();
List categories = plot.getCategories();
if (categories != null) {
Font font = getTickLabelFont();
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
int categoryIndex = 0;
float xx = 0.0f;
float yy = 0.0f;
Iterator iterator = categories.iterator();
while (iterator.hasNext()) {
Object category = iterator.next();
String label = category.toString();
Rectangle2D labelBounds = font.getStringBounds(label, frc);
LineMetrics metrics = font.getLineMetrics(label, frc);
if (edge == RectangleEdge.LEFT) {
xx = (float) (cursor - getTickLabelInsets().right - labelBounds.getWidth());
}
else {
xx = (float) (cursor + getTickLabelInsets().left);
}
yy = (float) (getCategoryMiddle(categoryIndex,
categories.size(),
plotArea, edge)
- metrics.getStrikethroughOffset() + 0.5f);
Tick tick = new Tick(category, label, xx, yy);
getTicks().add(tick);
categoryIndex = categoryIndex + 1;
}
}
}
/**
* 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 cursor the cursor location.
* @param plotArea the area where the plot and axes will be drawn.
* @param dataArea the area inside the axes.
* @param edge the location of the axis.
*/
public void refreshTicksHorizontal(Graphics2D g2, double cursor,
Rectangle2D plotArea, Rectangle2D dataArea,
RectangleEdge edge) {
this.maxTickLineCount = 1;
getTicks().clear();
CategoryPlot categoryPlot = (CategoryPlot) getPlot();
List categories = categoryPlot.getCategories();
if (categories != null) {
Font font = getTickLabelFont();
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
int categorySkip = 0;
int categoryIndex = 0;
float maxWidth = (float) (dataArea.getWidth() / categories.size() * 0.9f);
float xx = 0.0f;
float yy = 0.0f;
Iterator iterator = categories.iterator();
while (iterator.hasNext()) {
Object category = iterator.next();
if (categorySkip != 0) {
++categoryIndex;
--categorySkip;
continue;
}
String label = category.toString();
Rectangle2D labelBounds = font.getStringBounds(label, frc);
LineMetrics metrics = font.getLineMetrics(label, frc);
float catX = (float) getCategoryMiddle(categoryIndex,
categories.size(),
dataArea, edge);
if (isVerticalCategoryLabels()) {
xx = (float) (catX + labelBounds.getHeight() / 2 - metrics.getDescent());
if (edge == RectangleEdge.TOP) {
yy = (float) (cursor - getTickLabelInsets().bottom);
// - labelBounds.getWidth());
}
else {
yy = (float) (cursor + getTickLabelInsets().top + labelBounds.getWidth());
}
getTicks().add(new Tick(category, label, xx, yy));
if (this.skipCategoryLabelsToFit) {
categorySkip = (int) ((labelBounds.getHeight() - maxWidth / 2)
/ maxWidth) + 1;
}
}
else if (labelBounds.getWidth() > maxWidth) {
if (this.skipCategoryLabelsToFit) {
xx = (float) (catX - maxWidth / 2);
if (edge == RectangleEdge.TOP) {
yy = (float) (dataArea.getMinY() - getTickLabelInsets().bottom
- metrics.getDescent()
- metrics.getLeading());
}
else {
yy = (float) (dataArea.getMaxY() + getTickLabelInsets().top
+ metrics.getHeight()
- metrics.getDescent());
}
getTicks().add(new Tick(category, label, xx, yy));
categorySkip = (int) ((labelBounds.getWidth() - maxWidth / 2)
/ maxWidth) + 1;
}
else {
String[] labels = breakLine(label, (int) maxWidth, frc);
Tick[] ts = new Tick[labels.length];
for (int i = 0; i < labels.length; i++) {
labelBounds = font.getStringBounds(labels[i], frc);
xx = (float) (catX - labelBounds.getWidth() / 2);
if (edge == RectangleEdge.TOP) {
yy = (float) (dataArea.getMinY() - getTickLabelInsets().bottom
- (labels.length - i) * metrics.getHeight()
+ metrics.getAscent());
}
else {
yy = (float) (dataArea.getMaxY() + getTickLabelInsets().top
+ (i + 1) * (metrics.getHeight())
- metrics.getDescent());
}
ts[i] = new Tick(category, labels[i], xx, yy);
}
if (labels.length > this.maxTickLineCount) {
this.maxTickLineCount = labels.length;
}
getTicks().add(ts);
}
}
else {
xx = (float) (catX - labelBounds.getWidth() / 2);
if (edge == RectangleEdge.TOP) {
yy = (float) (dataArea.getMinY() - getTickLabelInsets().bottom
- metrics.getLeading()
- metrics.getDescent());
}
else {
yy = (float) (dataArea.getMaxY() + getTickLabelInsets().top
+ metrics.getHeight()
- metrics.getDescent());
}
getTicks().add(new Tick(category, label, xx, yy));
}
categoryIndex = categoryIndex + 1;
}
}
}
/**
* A utility method for determining the height of the tallest tick label.
*
* @param g2 the graphics device.
* @param drawArea the drawing area.
* @param vertical a flag indicating whether the tick labels are drawn vertically.
*
* @return The maximum tick label height.
*/
protected double getMaxTickLabelHeight(Graphics2D g2, Rectangle2D drawArea, boolean vertical) {
Font font = getTickLabelFont();
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
double maxHeight = 0.0;
if (vertical) {
Iterator iterator = getTicks().iterator();
while (iterator.hasNext()) {
Tick tick = (Tick) iterator.next();
Rectangle2D labelBounds = font.getStringBounds(tick.getText(), frc);
if (labelBounds.getWidth() > maxHeight) {
maxHeight = labelBounds.getWidth();
}
}
}
else {
LineMetrics metrics = font.getLineMetrics("Sample", frc);
maxHeight = (metrics.getHeight() * this.maxTickLineCount);
// - (metrics.getDescent() * (this.maxTickLineCount - 1));
}
return maxHeight;
}
/**
* Returns the maximum width of the ticks in the working list (that is set
* up by refreshTicks()).
*
* @param g2 the graphics device.
* @param plotArea the area within which the plot is to be drawn.
*
* @return the maximum width of the ticks in the working list.
*/
protected double getMaxTickLabelWidth(Graphics2D g2, Rectangle2D plotArea) {
double maxWidth = 0.0;
Font font = getTickLabelFont();
FontRenderContext frc = g2.getFontRenderContext();
Iterator iterator = getTicks().iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (obj instanceof Tick) {
Tick tick = (Tick) obj;
Rectangle2D labelBounds = font.getStringBounds(tick.getText(), frc);
if (labelBounds.getWidth() > maxWidth) {
maxWidth = labelBounds.getWidth();
}
}
else {
Tick[] ts = (Tick[]) obj;
for (int i = 0; i < ts.length; i++) {
Rectangle2D labelBounds = font.getStringBounds(ts[i].getText(), frc);
if (labelBounds.getWidth() > maxWidth) {
maxWidth = labelBounds.getWidth();
}
}
}
}
return maxWidth;
}
/**
* Breaks a line
*
* @param text string at break
* @param areaWidth width of tick area
* @param frc current Font Renderer Context
*
* @return array of breaked strings
*/
private String[] breakLine(String text, int areaWidth, FontRenderContext frc) {
ArrayList textList = new ArrayList(5);
int currWidth = areaWidth;
AttributedString as = new AttributedString(text, getTickLabelFont().getAttributes());
AttributedCharacterIterator aci = as.getIterator();
AffineTransform affine = new AffineTransform();
for (;;) {
LineBreakMeasurer measurer = new LineBreakMeasurer(aci, frc);
int maxWidth = 0, offset = 0;
TextLayout layout = measurer.nextLayout(currWidth);
while (layout != null) {
textList.add(text.substring(offset, offset + layout.getCharacterCount()));
int width = layout.getOutline(affine).getBounds().width;
if (maxWidth < width) {
maxWidth = width;
}
offset += layout.getCharacterCount();
layout = measurer.nextLayout(currWidth);
}
if (maxWidth > areaWidth) {
currWidth -= maxWidth - currWidth;
if (currWidth > 0) {
textList.clear();
continue;
}
}
break;
}
String[] texts = new String[textList.size()];
return (String[]) textList.toArray(texts);
}
}