0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * ----------------
0028: * BarRenderer.java
0029: * ----------------
0030: * (C) Copyright 2002-2007, by Object Refinery Limited.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): Christian W. Zuckschwerdt;
0034: *
0035: * $Id: BarRenderer.java,v 1.13.2.18 2007/05/18 10:28:27 mungady Exp $
0036: *
0037: * Changes
0038: * -------
0039: * 14-Mar-2002 : Version 1 (DG);
0040: * 23-May-2002 : Added tooltip generator to renderer (DG);
0041: * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
0042: * 25-Jun-2002 : Changed constructor to protected and removed redundant
0043: * code (DG);
0044: * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
0045: * clip values (DG);
0046: * 24-Sep-2002 : Added getLegendItem() method (DG);
0047: * 09-Oct-2002 : Modified constructor to include URL generator (DG);
0048: * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
0049: * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
0050: * 17-Jan-2003 : Moved plot classes into a separate package (DG);
0051: * 25-Mar-2003 : Implemented Serializable (DG);
0052: * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
0053: * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
0054: * 12-Jun-2003 : Updates for item labels (DG);
0055: * 30-Jul-2003 : Modified entity constructor (CZ);
0056: * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
0057: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
0058: * 07-Oct-2003 : Added renderer state (DG);
0059: * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
0060: * methods (DG);
0061: * 28-Oct-2003 : Added support for gradient paint on bars (DG);
0062: * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
0063: * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
0064: * overriding (DG);
0065: * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
0066: * label generators. Fixed equals() method (DG);
0067: * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
0068: * 05-Nov-2004 : Modified drawItem() signature (DG);
0069: * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
0070: * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
0071: * 18-May-2005 : Added configurable base value (DG);
0072: * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
0073: * 01-Dec-2005 : Update legend item to use/not use outline (DG);
0074: * ------------: JFreeChart 1.0.x ---------------------------------------------
0075: * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
0076: * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
0077: * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
0078: * bars) (DG);
0079: * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
0080: * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
0081: * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
0082: * 11-May-2007 : Check for visibility in getLegendItem() (DG);
0083: * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
0084: * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
0085: *
0086: */
0087:
0088: package org.jfree.chart.renderer.category;
0089:
0090: import java.awt.BasicStroke;
0091: import java.awt.Color;
0092: import java.awt.Font;
0093: import java.awt.GradientPaint;
0094: import java.awt.Graphics2D;
0095: import java.awt.Paint;
0096: import java.awt.Shape;
0097: import java.awt.Stroke;
0098: import java.awt.geom.Line2D;
0099: import java.awt.geom.Point2D;
0100: import java.awt.geom.Rectangle2D;
0101: import java.io.Serializable;
0102:
0103: import org.jfree.chart.LegendItem;
0104: import org.jfree.chart.axis.CategoryAxis;
0105: import org.jfree.chart.axis.ValueAxis;
0106: import org.jfree.chart.entity.EntityCollection;
0107: import org.jfree.chart.event.RendererChangeEvent;
0108: import org.jfree.chart.labels.CategoryItemLabelGenerator;
0109: import org.jfree.chart.labels.ItemLabelAnchor;
0110: import org.jfree.chart.labels.ItemLabelPosition;
0111: import org.jfree.chart.plot.CategoryPlot;
0112: import org.jfree.chart.plot.PlotOrientation;
0113: import org.jfree.chart.plot.PlotRenderingInfo;
0114: import org.jfree.data.Range;
0115: import org.jfree.data.category.CategoryDataset;
0116: import org.jfree.data.general.DatasetUtilities;
0117: import org.jfree.text.TextUtilities;
0118: import org.jfree.ui.GradientPaintTransformer;
0119: import org.jfree.ui.RectangleEdge;
0120: import org.jfree.ui.StandardGradientPaintTransformer;
0121: import org.jfree.util.ObjectUtilities;
0122: import org.jfree.util.PublicCloneable;
0123:
0124: /**
0125: * A {@link CategoryItemRenderer} that draws individual data items as bars.
0126: */
0127: public class BarRenderer extends AbstractCategoryItemRenderer implements
0128: Cloneable, PublicCloneable, Serializable {
0129:
0130: /** For serialization. */
0131: private static final long serialVersionUID = 6000649414965887481L;
0132:
0133: /** The default item margin percentage. */
0134: public static final double DEFAULT_ITEM_MARGIN = 0.20;
0135:
0136: /**
0137: * Constant that controls the minimum width before a bar has an outline
0138: * drawn.
0139: */
0140: public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
0141:
0142: /** The margin between items (bars) within a category. */
0143: private double itemMargin;
0144:
0145: /** A flag that controls whether or not bar outlines are drawn. */
0146: private boolean drawBarOutline;
0147:
0148: /** The maximum bar width as a percentage of the available space. */
0149: private double maximumBarWidth;
0150:
0151: /** The minimum bar length (in Java2D units). */
0152: private double minimumBarLength;
0153:
0154: /**
0155: * An optional class used to transform gradient paint objects to fit each
0156: * bar.
0157: */
0158: private GradientPaintTransformer gradientPaintTransformer;
0159:
0160: /**
0161: * The fallback position if a positive item label doesn't fit inside the
0162: * bar.
0163: */
0164: private ItemLabelPosition positiveItemLabelPositionFallback;
0165:
0166: /**
0167: * The fallback position if a negative item label doesn't fit inside the
0168: * bar.
0169: */
0170: private ItemLabelPosition negativeItemLabelPositionFallback;
0171:
0172: /** The upper clip (axis) value for the axis. */
0173: private double upperClip;
0174: // TODO: this needs to move into the renderer state
0175:
0176: /** The lower clip (axis) value for the axis. */
0177: private double lowerClip;
0178: // TODO: this needs to move into the renderer state
0179:
0180: /** The base value for the bars (defaults to 0.0). */
0181: private double base;
0182:
0183: /**
0184: * A flag that controls whether the base value is included in the range
0185: * returned by the findRangeBounds() method.
0186: */
0187: private boolean includeBaseInRange;
0188:
0189: /**
0190: * Creates a new bar renderer with default settings.
0191: */
0192: public BarRenderer() {
0193: super ();
0194: this .base = 0.0;
0195: this .includeBaseInRange = true;
0196: this .itemMargin = DEFAULT_ITEM_MARGIN;
0197: this .drawBarOutline = true;
0198: this .maximumBarWidth = 1.0;
0199: // 100 percent, so it will not apply unless changed
0200: this .positiveItemLabelPositionFallback = null;
0201: this .negativeItemLabelPositionFallback = null;
0202: this .gradientPaintTransformer = new StandardGradientPaintTransformer();
0203: this .minimumBarLength = 0.0;
0204: }
0205:
0206: /**
0207: * Returns the base value for the bars. The default value is
0208: * <code>0.0</code>.
0209: *
0210: * @return The base value for the bars.
0211: *
0212: * @see #setBase(double)
0213: */
0214: public double getBase() {
0215: return this .base;
0216: }
0217:
0218: /**
0219: * Sets the base value for the bars and sends a {@link RendererChangeEvent}
0220: * to all registered listeners.
0221: *
0222: * @param base the new base value.
0223: *
0224: * @see #getBase()
0225: */
0226: public void setBase(double base) {
0227: this .base = base;
0228: notifyListeners(new RendererChangeEvent(this ));
0229: }
0230:
0231: /**
0232: * Returns the item margin as a percentage of the available space for all
0233: * bars.
0234: *
0235: * @return The margin percentage (where 0.10 is ten percent).
0236: *
0237: * @see #setItemMargin(double)
0238: */
0239: public double getItemMargin() {
0240: return this .itemMargin;
0241: }
0242:
0243: /**
0244: * Sets the item margin and sends a {@link RendererChangeEvent} to all
0245: * registered listeners. The value is expressed as a percentage of the
0246: * available width for plotting all the bars, with the resulting amount to
0247: * be distributed between all the bars evenly.
0248: *
0249: * @param percent the margin (where 0.10 is ten percent).
0250: *
0251: * @see #getItemMargin()
0252: */
0253: public void setItemMargin(double percent) {
0254: this .itemMargin = percent;
0255: notifyListeners(new RendererChangeEvent(this ));
0256: }
0257:
0258: /**
0259: * Returns a flag that controls whether or not bar outlines are drawn.
0260: *
0261: * @return A boolean.
0262: *
0263: * @see #setDrawBarOutline(boolean)
0264: */
0265: public boolean isDrawBarOutline() {
0266: return this .drawBarOutline;
0267: }
0268:
0269: /**
0270: * Sets the flag that controls whether or not bar outlines are drawn and
0271: * sends a {@link RendererChangeEvent} to all registered listeners.
0272: *
0273: * @param draw the flag.
0274: *
0275: * @see #isDrawBarOutline()
0276: */
0277: public void setDrawBarOutline(boolean draw) {
0278: this .drawBarOutline = draw;
0279: notifyListeners(new RendererChangeEvent(this ));
0280: }
0281:
0282: /**
0283: * Returns the maximum bar width, as a percentage of the available drawing
0284: * space.
0285: *
0286: * @return The maximum bar width.
0287: *
0288: * @see #setMaximumBarWidth(double)
0289: */
0290: public double getMaximumBarWidth() {
0291: return this .maximumBarWidth;
0292: }
0293:
0294: /**
0295: * Sets the maximum bar width, which is specified as a percentage of the
0296: * available space for all bars, and sends a {@link RendererChangeEvent} to
0297: * all registered listeners.
0298: *
0299: * @param percent the percent (where 0.05 is five percent).
0300: *
0301: * @see #getMaximumBarWidth()
0302: */
0303: public void setMaximumBarWidth(double percent) {
0304: this .maximumBarWidth = percent;
0305: notifyListeners(new RendererChangeEvent(this ));
0306: }
0307:
0308: /**
0309: * Returns the minimum bar length (in Java2D units).
0310: *
0311: * @return The minimum bar length.
0312: *
0313: * @see #setMinimumBarLength(double)
0314: */
0315: public double getMinimumBarLength() {
0316: return this .minimumBarLength;
0317: }
0318:
0319: /**
0320: * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
0321: * all registered listeners. The minimum bar length is specified in Java2D
0322: * units, and can be used to prevent bars that represent very small data
0323: * values from disappearing when drawn on the screen.
0324: *
0325: * @param min the minimum bar length (in Java2D units).
0326: *
0327: * @see #getMinimumBarLength()
0328: */
0329: public void setMinimumBarLength(double min) {
0330: this .minimumBarLength = min;
0331: notifyListeners(new RendererChangeEvent(this ));
0332: }
0333:
0334: /**
0335: * Returns the gradient paint transformer (an object used to transform
0336: * gradient paint objects to fit each bar).
0337: *
0338: * @return A transformer (<code>null</code> possible).
0339: *
0340: * @see #setGradientPaintTransformer(GradientPaintTransformer)
0341: */
0342: public GradientPaintTransformer getGradientPaintTransformer() {
0343: return this .gradientPaintTransformer;
0344: }
0345:
0346: /**
0347: * Sets the gradient paint transformer and sends a
0348: * {@link RendererChangeEvent} to all registered listeners.
0349: *
0350: * @param transformer the transformer (<code>null</code> permitted).
0351: *
0352: * @see #getGradientPaintTransformer()
0353: */
0354: public void setGradientPaintTransformer(
0355: GradientPaintTransformer transformer) {
0356: this .gradientPaintTransformer = transformer;
0357: notifyListeners(new RendererChangeEvent(this ));
0358: }
0359:
0360: /**
0361: * Returns the fallback position for positive item labels that don't fit
0362: * within a bar.
0363: *
0364: * @return The fallback position (<code>null</code> possible).
0365: *
0366: * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
0367: */
0368: public ItemLabelPosition getPositiveItemLabelPositionFallback() {
0369: return this .positiveItemLabelPositionFallback;
0370: }
0371:
0372: /**
0373: * Sets the fallback position for positive item labels that don't fit
0374: * within a bar, and sends a {@link RendererChangeEvent} to all registered
0375: * listeners.
0376: *
0377: * @param position the position (<code>null</code> permitted).
0378: *
0379: * @see #getPositiveItemLabelPositionFallback()
0380: */
0381: public void setPositiveItemLabelPositionFallback(
0382: ItemLabelPosition position) {
0383: this .positiveItemLabelPositionFallback = position;
0384: notifyListeners(new RendererChangeEvent(this ));
0385: }
0386:
0387: /**
0388: * Returns the fallback position for negative item labels that don't fit
0389: * within a bar.
0390: *
0391: * @return The fallback position (<code>null</code> possible).
0392: *
0393: * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
0394: */
0395: public ItemLabelPosition getNegativeItemLabelPositionFallback() {
0396: return this .negativeItemLabelPositionFallback;
0397: }
0398:
0399: /**
0400: * Sets the fallback position for negative item labels that don't fit
0401: * within a bar, and sends a {@link RendererChangeEvent} to all registered
0402: * listeners.
0403: *
0404: * @param position the position (<code>null</code> permitted).
0405: *
0406: * @see #getNegativeItemLabelPositionFallback()
0407: */
0408: public void setNegativeItemLabelPositionFallback(
0409: ItemLabelPosition position) {
0410: this .negativeItemLabelPositionFallback = position;
0411: notifyListeners(new RendererChangeEvent(this ));
0412: }
0413:
0414: /**
0415: * Returns the flag that controls whether or not the base value for the
0416: * bars is included in the range calculated by
0417: * {@link #findRangeBounds(CategoryDataset)}.
0418: *
0419: * @return <code>true</code> if the base is included in the range, and
0420: * <code>false</code> otherwise.
0421: *
0422: * @since 1.0.1
0423: *
0424: * @see #setIncludeBaseInRange(boolean)
0425: */
0426: public boolean getIncludeBaseInRange() {
0427: return this .includeBaseInRange;
0428: }
0429:
0430: /**
0431: * Sets the flag that controls whether or not the base value for the bars
0432: * is included in the range calculated by
0433: * {@link #findRangeBounds(CategoryDataset)}. If the flag is changed,
0434: * a {@link RendererChangeEvent} is sent to all registered listeners.
0435: *
0436: * @param include the new value for the flag.
0437: *
0438: * @since 1.0.1
0439: *
0440: * @see #getIncludeBaseInRange()
0441: */
0442: public void setIncludeBaseInRange(boolean include) {
0443: if (this .includeBaseInRange != include) {
0444: this .includeBaseInRange = include;
0445: notifyListeners(new RendererChangeEvent(this ));
0446: }
0447: }
0448:
0449: /**
0450: * Returns the lower clip value. This value is recalculated in the
0451: * initialise() method.
0452: *
0453: * @return The value.
0454: */
0455: public double getLowerClip() {
0456: // TODO: this attribute should be transferred to the renderer state.
0457: return this .lowerClip;
0458: }
0459:
0460: /**
0461: * Returns the upper clip value. This value is recalculated in the
0462: * initialise() method.
0463: *
0464: * @return The value.
0465: */
0466: public double getUpperClip() {
0467: // TODO: this attribute should be transferred to the renderer state.
0468: return this .upperClip;
0469: }
0470:
0471: /**
0472: * Initialises the renderer and returns a state object that will be passed
0473: * to subsequent calls to the drawItem method. This method gets called
0474: * once at the start of the process of drawing a chart.
0475: *
0476: * @param g2 the graphics device.
0477: * @param dataArea the area in which the data is to be plotted.
0478: * @param plot the plot.
0479: * @param rendererIndex the renderer index.
0480: * @param info collects chart rendering information for return to caller.
0481: *
0482: * @return The renderer state.
0483: */
0484: public CategoryItemRendererState initialise(Graphics2D g2,
0485: Rectangle2D dataArea, CategoryPlot plot, int rendererIndex,
0486: PlotRenderingInfo info) {
0487:
0488: CategoryItemRendererState state = super .initialise(g2,
0489: dataArea, plot, rendererIndex, info);
0490:
0491: // get the clipping values...
0492: ValueAxis rangeAxis = plot
0493: .getRangeAxisForDataset(rendererIndex);
0494: this .lowerClip = rangeAxis.getRange().getLowerBound();
0495: this .upperClip = rangeAxis.getRange().getUpperBound();
0496:
0497: // calculate the bar width
0498: calculateBarWidth(plot, dataArea, rendererIndex, state);
0499:
0500: return state;
0501:
0502: }
0503:
0504: /**
0505: * Calculates the bar width and stores it in the renderer state.
0506: *
0507: * @param plot the plot.
0508: * @param dataArea the data area.
0509: * @param rendererIndex the renderer index.
0510: * @param state the renderer state.
0511: */
0512: protected void calculateBarWidth(CategoryPlot plot,
0513: Rectangle2D dataArea, int rendererIndex,
0514: CategoryItemRendererState state) {
0515:
0516: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
0517: CategoryDataset dataset = plot.getDataset(rendererIndex);
0518: if (dataset != null) {
0519: int columns = dataset.getColumnCount();
0520: int rows = dataset.getRowCount();
0521: double space = 0.0;
0522: PlotOrientation orientation = plot.getOrientation();
0523: if (orientation == PlotOrientation.HORIZONTAL) {
0524: space = dataArea.getHeight();
0525: } else if (orientation == PlotOrientation.VERTICAL) {
0526: space = dataArea.getWidth();
0527: }
0528: double maxWidth = space * getMaximumBarWidth();
0529: double categoryMargin = 0.0;
0530: double currentItemMargin = 0.0;
0531: if (columns > 1) {
0532: categoryMargin = domainAxis.getCategoryMargin();
0533: }
0534: if (rows > 1) {
0535: currentItemMargin = getItemMargin();
0536: }
0537: double used = space
0538: * (1 - domainAxis.getLowerMargin()
0539: - domainAxis.getUpperMargin()
0540: - categoryMargin - currentItemMargin);
0541: if ((rows * columns) > 0) {
0542: state.setBarWidth(Math.min(used / (rows * columns),
0543: maxWidth));
0544: } else {
0545: state.setBarWidth(Math.min(used, maxWidth));
0546: }
0547: }
0548: }
0549:
0550: /**
0551: * Calculates the coordinate of the first "side" of a bar. This will be
0552: * the minimum x-coordinate for a vertical bar, and the minimum
0553: * y-coordinate for a horizontal bar.
0554: *
0555: * @param plot the plot.
0556: * @param orientation the plot orientation.
0557: * @param dataArea the data area.
0558: * @param domainAxis the domain axis.
0559: * @param state the renderer state (has the bar width precalculated).
0560: * @param row the row index.
0561: * @param column the column index.
0562: *
0563: * @return The coordinate.
0564: */
0565: protected double calculateBarW0(CategoryPlot plot,
0566: PlotOrientation orientation, Rectangle2D dataArea,
0567: CategoryAxis domainAxis, CategoryItemRendererState state,
0568: int row, int column) {
0569: // calculate bar width...
0570: double space = 0.0;
0571: if (orientation == PlotOrientation.HORIZONTAL) {
0572: space = dataArea.getHeight();
0573: } else {
0574: space = dataArea.getWidth();
0575: }
0576: double barW0 = domainAxis.getCategoryStart(column,
0577: getColumnCount(), dataArea, plot.getDomainAxisEdge());
0578: int seriesCount = getRowCount();
0579: int categoryCount = getColumnCount();
0580: if (seriesCount > 1) {
0581: double seriesGap = space * getItemMargin()
0582: / (categoryCount * (seriesCount - 1));
0583: double seriesW = calculateSeriesWidth(space, domainAxis,
0584: categoryCount, seriesCount);
0585: barW0 = barW0 + row * (seriesW + seriesGap)
0586: + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
0587: } else {
0588: barW0 = domainAxis.getCategoryMiddle(column,
0589: getColumnCount(), dataArea, plot
0590: .getDomainAxisEdge())
0591: - state.getBarWidth() / 2.0;
0592: }
0593: return barW0;
0594: }
0595:
0596: /**
0597: * Calculates the coordinates for the length of a single bar.
0598: *
0599: * @param value the value represented by the bar.
0600: *
0601: * @return The coordinates for each end of the bar (or <code>null</code> if
0602: * the bar is not visible for the current axis range).
0603: */
0604: protected double[] calculateBarL0L1(double value) {
0605: double lclip = getLowerClip();
0606: double uclip = getUpperClip();
0607: double barLow = Math.min(this .base, value);
0608: double barHigh = Math.max(this .base, value);
0609: if (barHigh < lclip) { // bar is not visible
0610: return null;
0611: }
0612: if (barLow > uclip) { // bar is not visible
0613: return null;
0614: }
0615: barLow = Math.max(barLow, lclip);
0616: barHigh = Math.min(barHigh, uclip);
0617: return new double[] { barLow, barHigh };
0618: }
0619:
0620: /**
0621: * Returns the range of values the renderer requires to display all the
0622: * items from the specified dataset. This takes into account the range
0623: * of values in the dataset, plus the flag that determines whether or not
0624: * the base value for the bars should be included in the range.
0625: *
0626: * @param dataset the dataset (<code>null</code> permitted).
0627: *
0628: * @return The range (or <code>null</code> if the dataset is
0629: * <code>null</code> or empty).
0630: */
0631: public Range findRangeBounds(CategoryDataset dataset) {
0632: Range result = DatasetUtilities.findRangeBounds(dataset);
0633: if (result != null) {
0634: if (this .includeBaseInRange) {
0635: result = Range.expandToInclude(result, this .base);
0636: }
0637: }
0638: return result;
0639: }
0640:
0641: /**
0642: * Returns a legend item for a series.
0643: *
0644: * @param datasetIndex the dataset index (zero-based).
0645: * @param series the series index (zero-based).
0646: *
0647: * @return The legend item (possibly <code>null</code>).
0648: */
0649: public LegendItem getLegendItem(int datasetIndex, int series) {
0650:
0651: CategoryPlot cp = getPlot();
0652: if (cp == null) {
0653: return null;
0654: }
0655:
0656: // check that a legend item needs to be displayed...
0657: if (!isSeriesVisible(series)
0658: || !isSeriesVisibleInLegend(series)) {
0659: return null;
0660: }
0661:
0662: CategoryDataset dataset = cp.getDataset(datasetIndex);
0663: String label = getLegendItemLabelGenerator().generateLabel(
0664: dataset, series);
0665: String description = label;
0666: String toolTipText = null;
0667: if (getLegendItemToolTipGenerator() != null) {
0668: toolTipText = getLegendItemToolTipGenerator()
0669: .generateLabel(dataset, series);
0670: }
0671: String urlText = null;
0672: if (getLegendItemURLGenerator() != null) {
0673: urlText = getLegendItemURLGenerator().generateLabel(
0674: dataset, series);
0675: }
0676: Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
0677: Paint paint = lookupSeriesPaint(series);
0678: Paint outlinePaint = lookupSeriesOutlinePaint(series);
0679: Stroke outlineStroke = lookupSeriesOutlineStroke(series);
0680:
0681: LegendItem result = new LegendItem(label, description,
0682: toolTipText, urlText, true, shape, true, paint,
0683: isDrawBarOutline(), outlinePaint, outlineStroke, false,
0684: new Line2D.Float(), new BasicStroke(1.0f), Color.black);
0685: result.setDataset(dataset);
0686: result.setDatasetIndex(datasetIndex);
0687: result.setSeriesKey(dataset.getRowKey(series));
0688: result.setSeriesIndex(series);
0689: if (this .gradientPaintTransformer != null) {
0690: result
0691: .setFillPaintTransformer(this .gradientPaintTransformer);
0692: }
0693: return result;
0694: }
0695:
0696: /**
0697: * Draws the bar for a single (series, category) data item.
0698: *
0699: * @param g2 the graphics device.
0700: * @param state the renderer state.
0701: * @param dataArea the data area.
0702: * @param plot the plot.
0703: * @param domainAxis the domain axis.
0704: * @param rangeAxis the range axis.
0705: * @param dataset the dataset.
0706: * @param row the row index (zero-based).
0707: * @param column the column index (zero-based).
0708: * @param pass the pass index.
0709: */
0710: public void drawItem(Graphics2D g2,
0711: CategoryItemRendererState state, Rectangle2D dataArea,
0712: CategoryPlot plot, CategoryAxis domainAxis,
0713: ValueAxis rangeAxis, CategoryDataset dataset, int row,
0714: int column, int pass) {
0715:
0716: // nothing is drawn for null values...
0717: Number dataValue = dataset.getValue(row, column);
0718: if (dataValue == null) {
0719: return;
0720: }
0721:
0722: double value = dataValue.doubleValue();
0723:
0724: PlotOrientation orientation = plot.getOrientation();
0725: double barW0 = calculateBarW0(plot, orientation, dataArea,
0726: domainAxis, state, row, column);
0727: double[] barL0L1 = calculateBarL0L1(value);
0728: if (barL0L1 == null) {
0729: return; // the bar is not visible
0730: }
0731:
0732: RectangleEdge edge = plot.getRangeAxisEdge();
0733: double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea,
0734: edge);
0735: double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea,
0736: edge);
0737: double barL0 = Math.min(transL0, transL1);
0738: double barLength = Math.max(Math.abs(transL1 - transL0),
0739: getMinimumBarLength());
0740:
0741: // draw the bar...
0742: Rectangle2D bar = null;
0743: if (orientation == PlotOrientation.HORIZONTAL) {
0744: bar = new Rectangle2D.Double(barL0, barW0, barLength, state
0745: .getBarWidth());
0746: } else {
0747: bar = new Rectangle2D.Double(barW0, barL0, state
0748: .getBarWidth(), barLength);
0749: }
0750: Paint itemPaint = getItemPaint(row, column);
0751: GradientPaintTransformer t = getGradientPaintTransformer();
0752: if (t != null && itemPaint instanceof GradientPaint) {
0753: itemPaint = t.transform((GradientPaint) itemPaint, bar);
0754: }
0755: g2.setPaint(itemPaint);
0756: g2.fill(bar);
0757:
0758: // draw the outline...
0759: if (isDrawBarOutline()
0760: && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
0761: Stroke stroke = getItemOutlineStroke(row, column);
0762: Paint paint = getItemOutlinePaint(row, column);
0763: if (stroke != null && paint != null) {
0764: g2.setStroke(stroke);
0765: g2.setPaint(paint);
0766: g2.draw(bar);
0767: }
0768: }
0769:
0770: CategoryItemLabelGenerator generator = getItemLabelGenerator(
0771: row, column);
0772: if (generator != null && isItemLabelVisible(row, column)) {
0773: drawItemLabel(g2, dataset, row, column, plot, generator,
0774: bar, (value < 0.0));
0775: }
0776:
0777: // add an item entity, if this information is being collected
0778: EntityCollection entities = state.getEntityCollection();
0779: if (entities != null) {
0780: addItemEntity(entities, dataset, row, column, bar);
0781: }
0782:
0783: }
0784:
0785: /**
0786: * Calculates the available space for each series.
0787: *
0788: * @param space the space along the entire axis (in Java2D units).
0789: * @param axis the category axis.
0790: * @param categories the number of categories.
0791: * @param series the number of series.
0792: *
0793: * @return The width of one series.
0794: */
0795: protected double calculateSeriesWidth(double space,
0796: CategoryAxis axis, int categories, int series) {
0797: double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
0798: - axis.getUpperMargin();
0799: if (categories > 1) {
0800: factor = factor - axis.getCategoryMargin();
0801: }
0802: return (space * factor) / (categories * series);
0803: }
0804:
0805: /**
0806: * Draws an item label. This method is overridden so that the bar can be
0807: * used to calculate the label anchor point.
0808: *
0809: * @param g2 the graphics device.
0810: * @param data the dataset.
0811: * @param row the row.
0812: * @param column the column.
0813: * @param plot the plot.
0814: * @param generator the label generator.
0815: * @param bar the bar.
0816: * @param negative a flag indicating a negative value.
0817: */
0818: protected void drawItemLabel(Graphics2D g2, CategoryDataset data,
0819: int row, int column, CategoryPlot plot,
0820: CategoryItemLabelGenerator generator, Rectangle2D bar,
0821: boolean negative) {
0822:
0823: String label = generator.generateLabel(data, row, column);
0824: if (label == null) {
0825: return; // nothing to do
0826: }
0827:
0828: Font labelFont = getItemLabelFont(row, column);
0829: g2.setFont(labelFont);
0830: Paint paint = getItemLabelPaint(row, column);
0831: g2.setPaint(paint);
0832:
0833: // find out where to place the label...
0834: ItemLabelPosition position = null;
0835: if (!negative) {
0836: position = getPositiveItemLabelPosition(row, column);
0837: } else {
0838: position = getNegativeItemLabelPosition(row, column);
0839: }
0840:
0841: // work out the label anchor point...
0842: Point2D anchorPoint = calculateLabelAnchorPoint(position
0843: .getItemLabelAnchor(), bar, plot.getOrientation());
0844:
0845: if (isInternalAnchor(position.getItemLabelAnchor())) {
0846: Shape bounds = TextUtilities.calculateRotatedStringBounds(
0847: label, g2, (float) anchorPoint.getX(),
0848: (float) anchorPoint.getY(), position
0849: .getTextAnchor(), position.getAngle(),
0850: position.getRotationAnchor());
0851:
0852: if (bounds != null) {
0853: if (!bar.contains(bounds.getBounds2D())) {
0854: if (!negative) {
0855: position = getPositiveItemLabelPositionFallback();
0856: } else {
0857: position = getNegativeItemLabelPositionFallback();
0858: }
0859: if (position != null) {
0860: anchorPoint = calculateLabelAnchorPoint(
0861: position.getItemLabelAnchor(), bar,
0862: plot.getOrientation());
0863: }
0864: }
0865: }
0866:
0867: }
0868:
0869: if (position != null) {
0870: TextUtilities.drawRotatedString(label, g2,
0871: (float) anchorPoint.getX(), (float) anchorPoint
0872: .getY(), position.getTextAnchor(), position
0873: .getAngle(), position.getRotationAnchor());
0874: }
0875: }
0876:
0877: /**
0878: * Calculates the item label anchor point.
0879: *
0880: * @param anchor the anchor.
0881: * @param bar the bar.
0882: * @param orientation the plot orientation.
0883: *
0884: * @return The anchor point.
0885: */
0886: private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
0887: Rectangle2D bar, PlotOrientation orientation) {
0888:
0889: Point2D result = null;
0890: double offset = getItemLabelAnchorOffset();
0891: double x0 = bar.getX() - offset;
0892: double x1 = bar.getX();
0893: double x2 = bar.getX() + offset;
0894: double x3 = bar.getCenterX();
0895: double x4 = bar.getMaxX() - offset;
0896: double x5 = bar.getMaxX();
0897: double x6 = bar.getMaxX() + offset;
0898:
0899: double y0 = bar.getMaxY() + offset;
0900: double y1 = bar.getMaxY();
0901: double y2 = bar.getMaxY() - offset;
0902: double y3 = bar.getCenterY();
0903: double y4 = bar.getMinY() + offset;
0904: double y5 = bar.getMinY();
0905: double y6 = bar.getMinY() - offset;
0906:
0907: if (anchor == ItemLabelAnchor.CENTER) {
0908: result = new Point2D.Double(x3, y3);
0909: } else if (anchor == ItemLabelAnchor.INSIDE1) {
0910: result = new Point2D.Double(x4, y4);
0911: } else if (anchor == ItemLabelAnchor.INSIDE2) {
0912: result = new Point2D.Double(x4, y4);
0913: } else if (anchor == ItemLabelAnchor.INSIDE3) {
0914: result = new Point2D.Double(x4, y3);
0915: } else if (anchor == ItemLabelAnchor.INSIDE4) {
0916: result = new Point2D.Double(x4, y2);
0917: } else if (anchor == ItemLabelAnchor.INSIDE5) {
0918: result = new Point2D.Double(x4, y2);
0919: } else if (anchor == ItemLabelAnchor.INSIDE6) {
0920: result = new Point2D.Double(x3, y2);
0921: } else if (anchor == ItemLabelAnchor.INSIDE7) {
0922: result = new Point2D.Double(x2, y2);
0923: } else if (anchor == ItemLabelAnchor.INSIDE8) {
0924: result = new Point2D.Double(x2, y2);
0925: } else if (anchor == ItemLabelAnchor.INSIDE9) {
0926: result = new Point2D.Double(x2, y3);
0927: } else if (anchor == ItemLabelAnchor.INSIDE10) {
0928: result = new Point2D.Double(x2, y4);
0929: } else if (anchor == ItemLabelAnchor.INSIDE11) {
0930: result = new Point2D.Double(x2, y4);
0931: } else if (anchor == ItemLabelAnchor.INSIDE12) {
0932: result = new Point2D.Double(x3, y4);
0933: } else if (anchor == ItemLabelAnchor.OUTSIDE1) {
0934: result = new Point2D.Double(x5, y6);
0935: } else if (anchor == ItemLabelAnchor.OUTSIDE2) {
0936: result = new Point2D.Double(x6, y5);
0937: } else if (anchor == ItemLabelAnchor.OUTSIDE3) {
0938: result = new Point2D.Double(x6, y3);
0939: } else if (anchor == ItemLabelAnchor.OUTSIDE4) {
0940: result = new Point2D.Double(x6, y1);
0941: } else if (anchor == ItemLabelAnchor.OUTSIDE5) {
0942: result = new Point2D.Double(x5, y0);
0943: } else if (anchor == ItemLabelAnchor.OUTSIDE6) {
0944: result = new Point2D.Double(x3, y0);
0945: } else if (anchor == ItemLabelAnchor.OUTSIDE7) {
0946: result = new Point2D.Double(x1, y0);
0947: } else if (anchor == ItemLabelAnchor.OUTSIDE8) {
0948: result = new Point2D.Double(x0, y1);
0949: } else if (anchor == ItemLabelAnchor.OUTSIDE9) {
0950: result = new Point2D.Double(x0, y3);
0951: } else if (anchor == ItemLabelAnchor.OUTSIDE10) {
0952: result = new Point2D.Double(x0, y5);
0953: } else if (anchor == ItemLabelAnchor.OUTSIDE11) {
0954: result = new Point2D.Double(x1, y6);
0955: } else if (anchor == ItemLabelAnchor.OUTSIDE12) {
0956: result = new Point2D.Double(x3, y6);
0957: }
0958:
0959: return result;
0960:
0961: }
0962:
0963: /**
0964: * Returns <code>true</code> if the specified anchor point is inside a bar.
0965: *
0966: * @param anchor the anchor point.
0967: *
0968: * @return A boolean.
0969: */
0970: private boolean isInternalAnchor(ItemLabelAnchor anchor) {
0971: return anchor == ItemLabelAnchor.CENTER
0972: || anchor == ItemLabelAnchor.INSIDE1
0973: || anchor == ItemLabelAnchor.INSIDE2
0974: || anchor == ItemLabelAnchor.INSIDE3
0975: || anchor == ItemLabelAnchor.INSIDE4
0976: || anchor == ItemLabelAnchor.INSIDE5
0977: || anchor == ItemLabelAnchor.INSIDE6
0978: || anchor == ItemLabelAnchor.INSIDE7
0979: || anchor == ItemLabelAnchor.INSIDE8
0980: || anchor == ItemLabelAnchor.INSIDE9
0981: || anchor == ItemLabelAnchor.INSIDE10
0982: || anchor == ItemLabelAnchor.INSIDE11
0983: || anchor == ItemLabelAnchor.INSIDE12;
0984: }
0985:
0986: /**
0987: * Tests this instance for equality with an arbitrary object.
0988: *
0989: * @param obj the object (<code>null</code> permitted).
0990: *
0991: * @return A boolean.
0992: */
0993: public boolean equals(Object obj) {
0994:
0995: if (obj == this ) {
0996: return true;
0997: }
0998: if (!(obj instanceof BarRenderer)) {
0999: return false;
1000: }
1001: if (!super .equals(obj)) {
1002: return false;
1003: }
1004: BarRenderer that = (BarRenderer) obj;
1005: if (this .base != that.base) {
1006: return false;
1007: }
1008: if (this .itemMargin != that.itemMargin) {
1009: return false;
1010: }
1011: if (this .drawBarOutline != that.drawBarOutline) {
1012: return false;
1013: }
1014: if (this .maximumBarWidth != that.maximumBarWidth) {
1015: return false;
1016: }
1017: if (this .minimumBarLength != that.minimumBarLength) {
1018: return false;
1019: }
1020: if (!ObjectUtilities.equal(this .gradientPaintTransformer,
1021: that.gradientPaintTransformer)) {
1022: return false;
1023: }
1024: if (!ObjectUtilities.equal(
1025: this .positiveItemLabelPositionFallback,
1026: that.positiveItemLabelPositionFallback)) {
1027: return false;
1028: }
1029: if (!ObjectUtilities.equal(
1030: this .negativeItemLabelPositionFallback,
1031: that.negativeItemLabelPositionFallback)) {
1032: return false;
1033: }
1034: return true;
1035:
1036: }
1037:
1038: }
|