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: * SpiderWebPlot.java
0029: * ------------------
0030: * (C) Copyright 2005-2007, by Heaps of Flavour Pty Ltd and Contributors.
0031: *
0032: * Company Info: http://www.i4-talent.com
0033: *
0034: * Original Author: Don Elliott;
0035: * Contributor(s): David Gilbert (for Object Refinery Limited);
0036: * Nina Jeliazkova;
0037: *
0038: * $Id: SpiderWebPlot.java,v 1.11.2.16 2007/06/01 14:25:05 mungady Exp $
0039: *
0040: * Changes (from 28-Jan-2005)
0041: * --------------------------
0042: * 28-Jan-2005 : First cut - missing a few features - still to do:
0043: * - needs tooltips/URL/label generator functions
0044: * - ticks on axes / background grid?
0045: * 31-Jan-2005 : Renamed SpiderWebPlot, added label generator support, and
0046: * reformatted for consistency with other source files in
0047: * JFreeChart (DG);
0048: * 20-Apr-2005 : Renamed CategoryLabelGenerator
0049: * --> CategoryItemLabelGenerator (DG);
0050: * 05-May-2005 : Updated draw() method parameters (DG);
0051: * 10-Jun-2005 : Added equals() method and fixed serialization (DG);
0052: * 16-Jun-2005 : Added default constructor and get/setDataset()
0053: * methods (DG);
0054: * ------------- JFREECHART 1.0.x ---------------------------------------------
0055: * 05-Apr-2006 : Fixed bug preventing the display of zero values - see patch
0056: * 1462727 (DG);
0057: * 05-Apr-2006 : Added support for mouse clicks, tool tips and URLs - see patch
0058: * 1463455 (DG);
0059: * 01-Jun-2006 : Fix bug 1493199, NullPointerException when drawing with null
0060: * info (DG);
0061: * 05-Feb-2007 : Added attributes for axis stroke and paint, while fixing
0062: * bug 1651277, and implemented clone() properly (DG);
0063: * 06-Feb-2007 : Changed getPlotValue() to protected, as suggested in bug
0064: * 1605202 (DG);
0065: * 05-Mar-2007 : Restore clip region correctly (see bug 1667750) (DG);
0066: * 18-May-2007 : Set dataset for LegendItem (DG);
0067: *
0068: */
0069:
0070: package org.jfree.chart.plot;
0071:
0072: import java.awt.AlphaComposite;
0073: import java.awt.BasicStroke;
0074: import java.awt.Color;
0075: import java.awt.Composite;
0076: import java.awt.Font;
0077: import java.awt.Graphics2D;
0078: import java.awt.Paint;
0079: import java.awt.Polygon;
0080: import java.awt.Rectangle;
0081: import java.awt.Shape;
0082: import java.awt.Stroke;
0083: import java.awt.font.FontRenderContext;
0084: import java.awt.font.LineMetrics;
0085: import java.awt.geom.Arc2D;
0086: import java.awt.geom.Ellipse2D;
0087: import java.awt.geom.Line2D;
0088: import java.awt.geom.Point2D;
0089: import java.awt.geom.Rectangle2D;
0090: import java.io.IOException;
0091: import java.io.ObjectInputStream;
0092: import java.io.ObjectOutputStream;
0093: import java.io.Serializable;
0094: import java.util.Iterator;
0095: import java.util.List;
0096:
0097: import org.jfree.chart.LegendItem;
0098: import org.jfree.chart.LegendItemCollection;
0099: import org.jfree.chart.entity.CategoryItemEntity;
0100: import org.jfree.chart.entity.EntityCollection;
0101: import org.jfree.chart.event.PlotChangeEvent;
0102: import org.jfree.chart.labels.CategoryItemLabelGenerator;
0103: import org.jfree.chart.labels.CategoryToolTipGenerator;
0104: import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
0105: import org.jfree.chart.urls.CategoryURLGenerator;
0106: import org.jfree.data.category.CategoryDataset;
0107: import org.jfree.data.general.DatasetChangeEvent;
0108: import org.jfree.data.general.DatasetUtilities;
0109: import org.jfree.io.SerialUtilities;
0110: import org.jfree.ui.RectangleInsets;
0111: import org.jfree.util.ObjectUtilities;
0112: import org.jfree.util.PaintList;
0113: import org.jfree.util.PaintUtilities;
0114: import org.jfree.util.Rotation;
0115: import org.jfree.util.ShapeUtilities;
0116: import org.jfree.util.StrokeList;
0117: import org.jfree.util.TableOrder;
0118:
0119: /**
0120: * A plot that displays data from a {@link CategoryDataset} in the form of a
0121: * "spider web". Multiple series can be plotted on the same axis to allow
0122: * easy comparison. This plot doesn't support negative values at present.
0123: */
0124: public class SpiderWebPlot extends Plot implements Cloneable,
0125: Serializable {
0126:
0127: /** For serialization. */
0128: private static final long serialVersionUID = -5376340422031599463L;
0129:
0130: /** The default head radius percent (currently 1%). */
0131: public static final double DEFAULT_HEAD = 0.01;
0132:
0133: /** The default axis label gap (currently 10%). */
0134: public static final double DEFAULT_AXIS_LABEL_GAP = 0.10;
0135:
0136: /** The default interior gap. */
0137: public static final double DEFAULT_INTERIOR_GAP = 0.25;
0138:
0139: /** The maximum interior gap (currently 40%). */
0140: public static final double MAX_INTERIOR_GAP = 0.40;
0141:
0142: /** The default starting angle for the radar chart axes. */
0143: public static final double DEFAULT_START_ANGLE = 90.0;
0144:
0145: /** The default series label font. */
0146: public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif",
0147: Font.PLAIN, 10);
0148:
0149: /** The default series label paint. */
0150: public static final Paint DEFAULT_LABEL_PAINT = Color.black;
0151:
0152: /** The default series label background paint. */
0153: public static final Paint DEFAULT_LABEL_BACKGROUND_PAINT = new Color(
0154: 255, 255, 192);
0155:
0156: /** The default series label outline paint. */
0157: public static final Paint DEFAULT_LABEL_OUTLINE_PAINT = Color.black;
0158:
0159: /** The default series label outline stroke. */
0160: public static final Stroke DEFAULT_LABEL_OUTLINE_STROKE = new BasicStroke(
0161: 0.5f);
0162:
0163: /** The default series label shadow paint. */
0164: public static final Paint DEFAULT_LABEL_SHADOW_PAINT = Color.lightGray;
0165:
0166: /**
0167: * The default maximum value plotted - forces the plot to evaluate
0168: * the maximum from the data passed in
0169: */
0170: public static final double DEFAULT_MAX_VALUE = -1.0;
0171:
0172: /** The head radius as a percentage of the available drawing area. */
0173: protected double headPercent;
0174:
0175: /** The space left around the outside of the plot as a percentage. */
0176: private double interiorGap;
0177:
0178: /** The gap between the labels and the axes as a %age of the radius. */
0179: private double axisLabelGap;
0180:
0181: /**
0182: * The paint used to draw the axis lines.
0183: *
0184: * @since 1.0.4
0185: */
0186: private transient Paint axisLinePaint;
0187:
0188: /**
0189: * The stroke used to draw the axis lines.
0190: *
0191: * @since 1.0.4
0192: */
0193: private transient Stroke axisLineStroke;
0194:
0195: /** The dataset. */
0196: private CategoryDataset dataset;
0197:
0198: /** The maximum value we are plotting against on each category axis */
0199: private double maxValue;
0200:
0201: /**
0202: * The data extract order (BY_ROW or BY_COLUMN). This denotes whether
0203: * the data series are stored in rows (in which case the category names are
0204: * derived from the column keys) or in columns (in which case the category
0205: * names are derived from the row keys).
0206: */
0207: private TableOrder dataExtractOrder;
0208:
0209: /** The starting angle. */
0210: private double startAngle;
0211:
0212: /** The direction for drawing the radar axis & plots. */
0213: private Rotation direction;
0214:
0215: /** The legend item shape. */
0216: private transient Shape legendItemShape;
0217:
0218: /** The paint for ALL series (overrides list). */
0219: private transient Paint seriesPaint;
0220:
0221: /** The series paint list. */
0222: private PaintList seriesPaintList;
0223:
0224: /** The base series paint (fallback). */
0225: private transient Paint baseSeriesPaint;
0226:
0227: /** The outline paint for ALL series (overrides list). */
0228: private transient Paint seriesOutlinePaint;
0229:
0230: /** The series outline paint list. */
0231: private PaintList seriesOutlinePaintList;
0232:
0233: /** The base series outline paint (fallback). */
0234: private transient Paint baseSeriesOutlinePaint;
0235:
0236: /** The outline stroke for ALL series (overrides list). */
0237: private transient Stroke seriesOutlineStroke;
0238:
0239: /** The series outline stroke list. */
0240: private StrokeList seriesOutlineStrokeList;
0241:
0242: /** The base series outline stroke (fallback). */
0243: private transient Stroke baseSeriesOutlineStroke;
0244:
0245: /** The font used to display the category labels. */
0246: private Font labelFont;
0247:
0248: /** The color used to draw the category labels. */
0249: private transient Paint labelPaint;
0250:
0251: /** The label generator. */
0252: private CategoryItemLabelGenerator labelGenerator;
0253:
0254: /** controls if the web polygons are filled or not */
0255: private boolean webFilled = true;
0256:
0257: /** A tooltip generator for the plot (<code>null</code> permitted). */
0258: private CategoryToolTipGenerator toolTipGenerator;
0259:
0260: /** A URL generator for the plot (<code>null</code> permitted). */
0261: private CategoryURLGenerator urlGenerator;
0262:
0263: /**
0264: * Creates a default plot with no dataset.
0265: */
0266: public SpiderWebPlot() {
0267: this (null);
0268: }
0269:
0270: /**
0271: * Creates a new spider web plot with the given dataset, with each row
0272: * representing a series.
0273: *
0274: * @param dataset the dataset (<code>null</code> permitted).
0275: */
0276: public SpiderWebPlot(CategoryDataset dataset) {
0277: this (dataset, TableOrder.BY_ROW);
0278: }
0279:
0280: /**
0281: * Creates a new spider web plot with the given dataset.
0282: *
0283: * @param dataset the dataset.
0284: * @param extract controls how data is extracted ({@link TableOrder#BY_ROW}
0285: * or {@link TableOrder#BY_COLUMN}).
0286: */
0287: public SpiderWebPlot(CategoryDataset dataset, TableOrder extract) {
0288: super ();
0289: if (extract == null) {
0290: throw new IllegalArgumentException(
0291: "Null 'extract' argument.");
0292: }
0293: this .dataset = dataset;
0294: if (dataset != null) {
0295: dataset.addChangeListener(this );
0296: }
0297:
0298: this .dataExtractOrder = extract;
0299: this .headPercent = DEFAULT_HEAD;
0300: this .axisLabelGap = DEFAULT_AXIS_LABEL_GAP;
0301: this .axisLinePaint = Color.black;
0302: this .axisLineStroke = new BasicStroke(1.0f);
0303:
0304: this .interiorGap = DEFAULT_INTERIOR_GAP;
0305: this .startAngle = DEFAULT_START_ANGLE;
0306: this .direction = Rotation.CLOCKWISE;
0307: this .maxValue = DEFAULT_MAX_VALUE;
0308:
0309: this .seriesPaint = null;
0310: this .seriesPaintList = new PaintList();
0311: this .baseSeriesPaint = null;
0312:
0313: this .seriesOutlinePaint = null;
0314: this .seriesOutlinePaintList = new PaintList();
0315: this .baseSeriesOutlinePaint = DEFAULT_OUTLINE_PAINT;
0316:
0317: this .seriesOutlineStroke = null;
0318: this .seriesOutlineStrokeList = new StrokeList();
0319: this .baseSeriesOutlineStroke = DEFAULT_OUTLINE_STROKE;
0320:
0321: this .labelFont = DEFAULT_LABEL_FONT;
0322: this .labelPaint = DEFAULT_LABEL_PAINT;
0323: this .labelGenerator = new StandardCategoryItemLabelGenerator();
0324:
0325: this .legendItemShape = DEFAULT_LEGEND_ITEM_CIRCLE;
0326: }
0327:
0328: /**
0329: * Returns a short string describing the type of plot.
0330: *
0331: * @return The plot type.
0332: */
0333: public String getPlotType() {
0334: // return localizationResources.getString("Radar_Plot");
0335: return ("Spider Web Plot");
0336: }
0337:
0338: /**
0339: * Returns the dataset.
0340: *
0341: * @return The dataset (possibly <code>null</code>).
0342: *
0343: * @see #setDataset(CategoryDataset)
0344: */
0345: public CategoryDataset getDataset() {
0346: return this .dataset;
0347: }
0348:
0349: /**
0350: * Sets the dataset used by the plot and sends a {@link PlotChangeEvent}
0351: * to all registered listeners.
0352: *
0353: * @param dataset the dataset (<code>null</code> permitted).
0354: *
0355: * @see #getDataset()
0356: */
0357: public void setDataset(CategoryDataset dataset) {
0358: // if there is an existing dataset, remove the plot from the list of
0359: // change listeners...
0360: if (this .dataset != null) {
0361: this .dataset.removeChangeListener(this );
0362: }
0363:
0364: // set the new dataset, and register the chart as a change listener...
0365: this .dataset = dataset;
0366: if (dataset != null) {
0367: setDatasetGroup(dataset.getGroup());
0368: dataset.addChangeListener(this );
0369: }
0370:
0371: // send a dataset change event to self to trigger plot change event
0372: datasetChanged(new DatasetChangeEvent(this , dataset));
0373: }
0374:
0375: /**
0376: * Method to determine if the web chart is to be filled.
0377: *
0378: * @return A boolean.
0379: *
0380: * @see #setWebFilled(boolean)
0381: */
0382: public boolean isWebFilled() {
0383: return this .webFilled;
0384: }
0385:
0386: /**
0387: * Sets the webFilled flag and sends a {@link PlotChangeEvent} to all
0388: * registered listeners.
0389: *
0390: * @param flag the flag.
0391: *
0392: * @see #isWebFilled()
0393: */
0394: public void setWebFilled(boolean flag) {
0395: this .webFilled = flag;
0396: notifyListeners(new PlotChangeEvent(this ));
0397: }
0398:
0399: /**
0400: * Returns the data extract order (by row or by column).
0401: *
0402: * @return The data extract order (never <code>null</code>).
0403: *
0404: * @see #setDataExtractOrder(TableOrder)
0405: */
0406: public TableOrder getDataExtractOrder() {
0407: return this .dataExtractOrder;
0408: }
0409:
0410: /**
0411: * Sets the data extract order (by row or by column) and sends a
0412: * {@link PlotChangeEvent}to all registered listeners.
0413: *
0414: * @param order the order (<code>null</code> not permitted).
0415: *
0416: * @throws IllegalArgumentException if <code>order</code> is
0417: * <code>null</code>.
0418: *
0419: * @see #getDataExtractOrder()
0420: */
0421: public void setDataExtractOrder(TableOrder order) {
0422: if (order == null) {
0423: throw new IllegalArgumentException("Null 'order' argument");
0424: }
0425: this .dataExtractOrder = order;
0426: notifyListeners(new PlotChangeEvent(this ));
0427: }
0428:
0429: /**
0430: * Returns the head percent.
0431: *
0432: * @return The head percent.
0433: *
0434: * @see #setHeadPercent(double)
0435: */
0436: public double getHeadPercent() {
0437: return this .headPercent;
0438: }
0439:
0440: /**
0441: * Sets the head percent and sends a {@link PlotChangeEvent} to all
0442: * registered listeners.
0443: *
0444: * @param percent the percent.
0445: *
0446: * @see #getHeadPercent()
0447: */
0448: public void setHeadPercent(double percent) {
0449: this .headPercent = percent;
0450: notifyListeners(new PlotChangeEvent(this ));
0451: }
0452:
0453: /**
0454: * Returns the start angle for the first radar axis.
0455: * <BR>
0456: * This is measured in degrees starting from 3 o'clock (Java Arc2D default)
0457: * and measuring anti-clockwise.
0458: *
0459: * @return The start angle.
0460: *
0461: * @see #setStartAngle(double)
0462: */
0463: public double getStartAngle() {
0464: return this .startAngle;
0465: }
0466:
0467: /**
0468: * Sets the starting angle and sends a {@link PlotChangeEvent} to all
0469: * registered listeners.
0470: * <P>
0471: * The initial default value is 90 degrees, which corresponds to 12 o'clock.
0472: * A value of zero corresponds to 3 o'clock... this is the encoding used by
0473: * Java's Arc2D class.
0474: *
0475: * @param angle the angle (in degrees).
0476: *
0477: * @see #getStartAngle()
0478: */
0479: public void setStartAngle(double angle) {
0480: this .startAngle = angle;
0481: notifyListeners(new PlotChangeEvent(this ));
0482: }
0483:
0484: /**
0485: * Returns the maximum value any category axis can take.
0486: *
0487: * @return The maximum value.
0488: *
0489: * @see #setMaxValue(double)
0490: */
0491: public double getMaxValue() {
0492: return this .maxValue;
0493: }
0494:
0495: /**
0496: * Sets the maximum value any category axis can take and sends
0497: * a {@link PlotChangeEvent} to all registered listeners.
0498: *
0499: * @param value the maximum value.
0500: *
0501: * @see #getMaxValue()
0502: */
0503: public void setMaxValue(double value) {
0504: this .maxValue = value;
0505: notifyListeners(new PlotChangeEvent(this ));
0506: }
0507:
0508: /**
0509: * Returns the direction in which the radar axes are drawn
0510: * (clockwise or anti-clockwise).
0511: *
0512: * @return The direction (never <code>null</code>).
0513: *
0514: * @see #setDirection(Rotation)
0515: */
0516: public Rotation getDirection() {
0517: return this .direction;
0518: }
0519:
0520: /**
0521: * Sets the direction in which the radar axes are drawn and sends a
0522: * {@link PlotChangeEvent} to all registered listeners.
0523: *
0524: * @param direction the direction (<code>null</code> not permitted).
0525: *
0526: * @see #getDirection()
0527: */
0528: public void setDirection(Rotation direction) {
0529: if (direction == null) {
0530: throw new IllegalArgumentException(
0531: "Null 'direction' argument.");
0532: }
0533: this .direction = direction;
0534: notifyListeners(new PlotChangeEvent(this ));
0535: }
0536:
0537: /**
0538: * Returns the interior gap, measured as a percentage of the available
0539: * drawing space.
0540: *
0541: * @return The gap (as a percentage of the available drawing space).
0542: *
0543: * @see #setInteriorGap(double)
0544: */
0545: public double getInteriorGap() {
0546: return this .interiorGap;
0547: }
0548:
0549: /**
0550: * Sets the interior gap and sends a {@link PlotChangeEvent} to all
0551: * registered listeners. This controls the space between the edges of the
0552: * plot and the plot area itself (the region where the axis labels appear).
0553: *
0554: * @param percent the gap (as a percentage of the available drawing space).
0555: *
0556: * @see #getInteriorGap()
0557: */
0558: public void setInteriorGap(double percent) {
0559: if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) {
0560: throw new IllegalArgumentException(
0561: "Percentage outside valid range.");
0562: }
0563: if (this .interiorGap != percent) {
0564: this .interiorGap = percent;
0565: notifyListeners(new PlotChangeEvent(this ));
0566: }
0567: }
0568:
0569: /**
0570: * Returns the axis label gap.
0571: *
0572: * @return The axis label gap.
0573: *
0574: * @see #setAxisLabelGap(double)
0575: */
0576: public double getAxisLabelGap() {
0577: return this .axisLabelGap;
0578: }
0579:
0580: /**
0581: * Sets the axis label gap and sends a {@link PlotChangeEvent} to all
0582: * registered listeners.
0583: *
0584: * @param gap the gap.
0585: *
0586: * @see #getAxisLabelGap()
0587: */
0588: public void setAxisLabelGap(double gap) {
0589: this .axisLabelGap = gap;
0590: notifyListeners(new PlotChangeEvent(this ));
0591: }
0592:
0593: /**
0594: * Returns the paint used to draw the axis lines.
0595: *
0596: * @return The paint used to draw the axis lines (never <code>null</code>).
0597: *
0598: * @see #setAxisLinePaint(Paint)
0599: * @see #getAxisLineStroke()
0600: * @since 1.0.4
0601: */
0602: public Paint getAxisLinePaint() {
0603: return this .axisLinePaint;
0604: }
0605:
0606: /**
0607: * Sets the paint used to draw the axis lines and sends a
0608: * {@link PlotChangeEvent} to all registered listeners.
0609: *
0610: * @param paint the paint (<code>null</code> not permitted).
0611: *
0612: * @see #getAxisLinePaint()
0613: * @since 1.0.4
0614: */
0615: public void setAxisLinePaint(Paint paint) {
0616: if (paint == null) {
0617: throw new IllegalArgumentException("Null 'paint' argument.");
0618: }
0619: this .axisLinePaint = paint;
0620: notifyListeners(new PlotChangeEvent(this ));
0621: }
0622:
0623: /**
0624: * Returns the stroke used to draw the axis lines.
0625: *
0626: * @return The stroke used to draw the axis lines (never <code>null</code>).
0627: *
0628: * @see #setAxisLineStroke(Stroke)
0629: * @see #getAxisLinePaint()
0630: * @since 1.0.4
0631: */
0632: public Stroke getAxisLineStroke() {
0633: return this .axisLineStroke;
0634: }
0635:
0636: /**
0637: * Sets the stroke used to draw the axis lines and sends a
0638: * {@link PlotChangeEvent} to all registered listeners.
0639: *
0640: * @param stroke the stroke (<code>null</code> not permitted).
0641: *
0642: * @see #getAxisLineStroke()
0643: * @since 1.0.4
0644: */
0645: public void setAxisLineStroke(Stroke stroke) {
0646: if (stroke == null) {
0647: throw new IllegalArgumentException(
0648: "Null 'stroke' argument.");
0649: }
0650: this .axisLineStroke = stroke;
0651: notifyListeners(new PlotChangeEvent(this ));
0652: }
0653:
0654: //// SERIES PAINT /////////////////////////
0655:
0656: /**
0657: * Returns the paint for ALL series in the plot.
0658: *
0659: * @return The paint (possibly <code>null</code>).
0660: *
0661: * @see #setSeriesPaint(Paint)
0662: */
0663: public Paint getSeriesPaint() {
0664: return this .seriesPaint;
0665: }
0666:
0667: /**
0668: * Sets the paint for ALL series in the plot. If this is set to</code> null
0669: * </code>, then a list of paints is used instead (to allow different colors
0670: * to be used for each series of the radar group).
0671: *
0672: * @param paint the paint (<code>null</code> permitted).
0673: *
0674: * @see #getSeriesPaint()
0675: */
0676: public void setSeriesPaint(Paint paint) {
0677: this .seriesPaint = paint;
0678: notifyListeners(new PlotChangeEvent(this ));
0679: }
0680:
0681: /**
0682: * Returns the paint for the specified series.
0683: *
0684: * @param series the series index (zero-based).
0685: *
0686: * @return The paint (never <code>null</code>).
0687: *
0688: * @see #setSeriesPaint(int, Paint)
0689: */
0690: public Paint getSeriesPaint(int series) {
0691:
0692: // return the override, if there is one...
0693: if (this .seriesPaint != null) {
0694: return this .seriesPaint;
0695: }
0696:
0697: // otherwise look up the paint list
0698: Paint result = this .seriesPaintList.getPaint(series);
0699: if (result == null) {
0700: DrawingSupplier supplier = getDrawingSupplier();
0701: if (supplier != null) {
0702: Paint p = supplier.getNextPaint();
0703: this .seriesPaintList.setPaint(series, p);
0704: result = p;
0705: } else {
0706: result = this .baseSeriesPaint;
0707: }
0708: }
0709: return result;
0710:
0711: }
0712:
0713: /**
0714: * Sets the paint used to fill a series of the radar and sends a
0715: * {@link PlotChangeEvent} to all registered listeners.
0716: *
0717: * @param series the series index (zero-based).
0718: * @param paint the paint (<code>null</code> permitted).
0719: *
0720: * @see #getSeriesPaint(int)
0721: */
0722: public void setSeriesPaint(int series, Paint paint) {
0723: this .seriesPaintList.setPaint(series, paint);
0724: notifyListeners(new PlotChangeEvent(this ));
0725: }
0726:
0727: /**
0728: * Returns the base series paint. This is used when no other paint is
0729: * available.
0730: *
0731: * @return The paint (never <code>null</code>).
0732: *
0733: * @see #setBaseSeriesPaint(Paint)
0734: */
0735: public Paint getBaseSeriesPaint() {
0736: return this .baseSeriesPaint;
0737: }
0738:
0739: /**
0740: * Sets the base series paint.
0741: *
0742: * @param paint the paint (<code>null</code> not permitted).
0743: *
0744: * @see #getBaseSeriesPaint()
0745: */
0746: public void setBaseSeriesPaint(Paint paint) {
0747: if (paint == null) {
0748: throw new IllegalArgumentException("Null 'paint' argument.");
0749: }
0750: this .baseSeriesPaint = paint;
0751: notifyListeners(new PlotChangeEvent(this ));
0752: }
0753:
0754: //// SERIES OUTLINE PAINT ////////////////////////////
0755:
0756: /**
0757: * Returns the outline paint for ALL series in the plot.
0758: *
0759: * @return The paint (possibly <code>null</code>).
0760: */
0761: public Paint getSeriesOutlinePaint() {
0762: return this .seriesOutlinePaint;
0763: }
0764:
0765: /**
0766: * Sets the outline paint for ALL series in the plot. If this is set to
0767: * </code> null</code>, then a list of paints is used instead (to allow
0768: * different colors to be used for each series).
0769: *
0770: * @param paint the paint (<code>null</code> permitted).
0771: */
0772: public void setSeriesOutlinePaint(Paint paint) {
0773: this .seriesOutlinePaint = paint;
0774: notifyListeners(new PlotChangeEvent(this ));
0775: }
0776:
0777: /**
0778: * Returns the paint for the specified series.
0779: *
0780: * @param series the series index (zero-based).
0781: *
0782: * @return The paint (never <code>null</code>).
0783: */
0784: public Paint getSeriesOutlinePaint(int series) {
0785: // return the override, if there is one...
0786: if (this .seriesOutlinePaint != null) {
0787: return this .seriesOutlinePaint;
0788: }
0789: // otherwise look up the paint list
0790: Paint result = this .seriesOutlinePaintList.getPaint(series);
0791: if (result == null) {
0792: result = this .baseSeriesOutlinePaint;
0793: }
0794: return result;
0795: }
0796:
0797: /**
0798: * Sets the paint used to fill a series of the radar and sends a
0799: * {@link PlotChangeEvent} to all registered listeners.
0800: *
0801: * @param series the series index (zero-based).
0802: * @param paint the paint (<code>null</code> permitted).
0803: */
0804: public void setSeriesOutlinePaint(int series, Paint paint) {
0805: this .seriesOutlinePaintList.setPaint(series, paint);
0806: notifyListeners(new PlotChangeEvent(this ));
0807: }
0808:
0809: /**
0810: * Returns the base series paint. This is used when no other paint is
0811: * available.
0812: *
0813: * @return The paint (never <code>null</code>).
0814: */
0815: public Paint getBaseSeriesOutlinePaint() {
0816: return this .baseSeriesOutlinePaint;
0817: }
0818:
0819: /**
0820: * Sets the base series paint.
0821: *
0822: * @param paint the paint (<code>null</code> not permitted).
0823: */
0824: public void setBaseSeriesOutlinePaint(Paint paint) {
0825: if (paint == null) {
0826: throw new IllegalArgumentException("Null 'paint' argument.");
0827: }
0828: this .baseSeriesOutlinePaint = paint;
0829: notifyListeners(new PlotChangeEvent(this ));
0830: }
0831:
0832: //// SERIES OUTLINE STROKE /////////////////////
0833:
0834: /**
0835: * Returns the outline stroke for ALL series in the plot.
0836: *
0837: * @return The stroke (possibly <code>null</code>).
0838: */
0839: public Stroke getSeriesOutlineStroke() {
0840: return this .seriesOutlineStroke;
0841: }
0842:
0843: /**
0844: * Sets the outline stroke for ALL series in the plot. If this is set to
0845: * </code> null</code>, then a list of paints is used instead (to allow
0846: * different colors to be used for each series).
0847: *
0848: * @param stroke the stroke (<code>null</code> permitted).
0849: */
0850: public void setSeriesOutlineStroke(Stroke stroke) {
0851: this .seriesOutlineStroke = stroke;
0852: notifyListeners(new PlotChangeEvent(this ));
0853: }
0854:
0855: /**
0856: * Returns the stroke for the specified series.
0857: *
0858: * @param series the series index (zero-based).
0859: *
0860: * @return The stroke (never <code>null</code>).
0861: */
0862: public Stroke getSeriesOutlineStroke(int series) {
0863:
0864: // return the override, if there is one...
0865: if (this .seriesOutlineStroke != null) {
0866: return this .seriesOutlineStroke;
0867: }
0868:
0869: // otherwise look up the paint list
0870: Stroke result = this .seriesOutlineStrokeList.getStroke(series);
0871: if (result == null) {
0872: result = this .baseSeriesOutlineStroke;
0873: }
0874: return result;
0875:
0876: }
0877:
0878: /**
0879: * Sets the stroke used to fill a series of the radar and sends a
0880: * {@link PlotChangeEvent} to all registered listeners.
0881: *
0882: * @param series the series index (zero-based).
0883: * @param stroke the stroke (<code>null</code> permitted).
0884: */
0885: public void setSeriesOutlineStroke(int series, Stroke stroke) {
0886: this .seriesOutlineStrokeList.setStroke(series, stroke);
0887: notifyListeners(new PlotChangeEvent(this ));
0888: }
0889:
0890: /**
0891: * Returns the base series stroke. This is used when no other stroke is
0892: * available.
0893: *
0894: * @return The stroke (never <code>null</code>).
0895: */
0896: public Stroke getBaseSeriesOutlineStroke() {
0897: return this .baseSeriesOutlineStroke;
0898: }
0899:
0900: /**
0901: * Sets the base series stroke.
0902: *
0903: * @param stroke the stroke (<code>null</code> not permitted).
0904: */
0905: public void setBaseSeriesOutlineStroke(Stroke stroke) {
0906: if (stroke == null) {
0907: throw new IllegalArgumentException(
0908: "Null 'stroke' argument.");
0909: }
0910: this .baseSeriesOutlineStroke = stroke;
0911: notifyListeners(new PlotChangeEvent(this ));
0912: }
0913:
0914: /**
0915: * Returns the shape used for legend items.
0916: *
0917: * @return The shape (never <code>null</code>).
0918: *
0919: * @see #setLegendItemShape(Shape)
0920: */
0921: public Shape getLegendItemShape() {
0922: return this .legendItemShape;
0923: }
0924:
0925: /**
0926: * Sets the shape used for legend items and sends a {@link PlotChangeEvent}
0927: * to all registered listeners.
0928: *
0929: * @param shape the shape (<code>null</code> not permitted).
0930: *
0931: * @see #getLegendItemShape()
0932: */
0933: public void setLegendItemShape(Shape shape) {
0934: if (shape == null) {
0935: throw new IllegalArgumentException("Null 'shape' argument.");
0936: }
0937: this .legendItemShape = shape;
0938: notifyListeners(new PlotChangeEvent(this ));
0939: }
0940:
0941: /**
0942: * Returns the series label font.
0943: *
0944: * @return The font (never <code>null</code>).
0945: *
0946: * @see #setLabelFont(Font)
0947: */
0948: public Font getLabelFont() {
0949: return this .labelFont;
0950: }
0951:
0952: /**
0953: * Sets the series label font and sends a {@link PlotChangeEvent} to all
0954: * registered listeners.
0955: *
0956: * @param font the font (<code>null</code> not permitted).
0957: *
0958: * @see #getLabelFont()
0959: */
0960: public void setLabelFont(Font font) {
0961: if (font == null) {
0962: throw new IllegalArgumentException("Null 'font' argument.");
0963: }
0964: this .labelFont = font;
0965: notifyListeners(new PlotChangeEvent(this ));
0966: }
0967:
0968: /**
0969: * Returns the series label paint.
0970: *
0971: * @return The paint (never <code>null</code>).
0972: *
0973: * @see #setLabelPaint(Paint)
0974: */
0975: public Paint getLabelPaint() {
0976: return this .labelPaint;
0977: }
0978:
0979: /**
0980: * Sets the series label paint and sends a {@link PlotChangeEvent} to all
0981: * registered listeners.
0982: *
0983: * @param paint the paint (<code>null</code> not permitted).
0984: *
0985: * @see #getLabelPaint()
0986: */
0987: public void setLabelPaint(Paint paint) {
0988: if (paint == null) {
0989: throw new IllegalArgumentException("Null 'paint' argument.");
0990: }
0991: this .labelPaint = paint;
0992: notifyListeners(new PlotChangeEvent(this ));
0993: }
0994:
0995: /**
0996: * Returns the label generator.
0997: *
0998: * @return The label generator (never <code>null</code>).
0999: *
1000: * @see #setLabelGenerator(CategoryItemLabelGenerator)
1001: */
1002: public CategoryItemLabelGenerator getLabelGenerator() {
1003: return this .labelGenerator;
1004: }
1005:
1006: /**
1007: * Sets the label generator and sends a {@link PlotChangeEvent} to all
1008: * registered listeners.
1009: *
1010: * @param generator the generator (<code>null</code> not permitted).
1011: *
1012: * @see #getLabelGenerator()
1013: */
1014: public void setLabelGenerator(CategoryItemLabelGenerator generator) {
1015: if (generator == null) {
1016: throw new IllegalArgumentException(
1017: "Null 'generator' argument.");
1018: }
1019: this .labelGenerator = generator;
1020: }
1021:
1022: /**
1023: * Returns the tool tip generator for the plot.
1024: *
1025: * @return The tool tip generator (possibly <code>null</code>).
1026: *
1027: * @see #setToolTipGenerator(CategoryToolTipGenerator)
1028: *
1029: * @since 1.0.2
1030: */
1031: public CategoryToolTipGenerator getToolTipGenerator() {
1032: return this .toolTipGenerator;
1033: }
1034:
1035: /**
1036: * Sets the tool tip generator for the plot and sends a
1037: * {@link PlotChangeEvent} to all registered listeners.
1038: *
1039: * @param generator the generator (<code>null</code> permitted).
1040: *
1041: * @see #getToolTipGenerator()
1042: *
1043: * @since 1.0.2
1044: */
1045: public void setToolTipGenerator(CategoryToolTipGenerator generator) {
1046: this .toolTipGenerator = generator;
1047: this .notifyListeners(new PlotChangeEvent(this ));
1048: }
1049:
1050: /**
1051: * Returns the URL generator for the plot.
1052: *
1053: * @return The URL generator (possibly <code>null</code>).
1054: *
1055: * @see #setURLGenerator(CategoryURLGenerator)
1056: *
1057: * @since 1.0.2
1058: */
1059: public CategoryURLGenerator getURLGenerator() {
1060: return this .urlGenerator;
1061: }
1062:
1063: /**
1064: * Sets the URL generator for the plot and sends a
1065: * {@link PlotChangeEvent} to all registered listeners.
1066: *
1067: * @param generator the generator (<code>null</code> permitted).
1068: *
1069: * @see #getURLGenerator()
1070: *
1071: * @since 1.0.2
1072: */
1073: public void setURLGenerator(CategoryURLGenerator generator) {
1074: this .urlGenerator = generator;
1075: this .notifyListeners(new PlotChangeEvent(this ));
1076: }
1077:
1078: /**
1079: * Returns a collection of legend items for the radar chart.
1080: *
1081: * @return The legend items.
1082: */
1083: public LegendItemCollection getLegendItems() {
1084: LegendItemCollection result = new LegendItemCollection();
1085:
1086: List keys = null;
1087:
1088: if (this .dataExtractOrder == TableOrder.BY_ROW) {
1089: keys = this .dataset.getRowKeys();
1090: } else if (this .dataExtractOrder == TableOrder.BY_COLUMN) {
1091: keys = this .dataset.getColumnKeys();
1092: }
1093:
1094: if (keys != null) {
1095: int series = 0;
1096: Iterator iterator = keys.iterator();
1097: Shape shape = getLegendItemShape();
1098:
1099: while (iterator.hasNext()) {
1100: String label = iterator.next().toString();
1101: String description = label;
1102:
1103: Paint paint = getSeriesPaint(series);
1104: Paint outlinePaint = getSeriesOutlinePaint(series);
1105: Stroke stroke = getSeriesOutlineStroke(series);
1106: LegendItem item = new LegendItem(label, description,
1107: null, null, shape, paint, stroke, outlinePaint);
1108: item.setDataset(getDataset());
1109: result.add(item);
1110: series++;
1111: }
1112: }
1113:
1114: return result;
1115: }
1116:
1117: /**
1118: * Returns a cartesian point from a polar angle, length and bounding box
1119: *
1120: * @param bounds the area inside which the point needs to be.
1121: * @param angle the polar angle, in degrees.
1122: * @param length the relative length. Given in percent of maximum extend.
1123: *
1124: * @return The cartesian point.
1125: */
1126: protected Point2D getWebPoint(Rectangle2D bounds, double angle,
1127: double length) {
1128:
1129: double angrad = Math.toRadians(angle);
1130: double x = Math.cos(angrad) * length * bounds.getWidth() / 2;
1131: double y = -Math.sin(angrad) * length * bounds.getHeight() / 2;
1132:
1133: return new Point2D.Double(bounds.getX() + x + bounds.getWidth()
1134: / 2, bounds.getY() + y + bounds.getHeight() / 2);
1135: }
1136:
1137: /**
1138: * Draws the plot on a Java 2D graphics device (such as the screen or a
1139: * printer).
1140: *
1141: * @param g2 the graphics device.
1142: * @param area the area within which the plot should be drawn.
1143: * @param anchor the anchor point (<code>null</code> permitted).
1144: * @param parentState the state from the parent plot, if there is one.
1145: * @param info collects info about the drawing.
1146: */
1147: public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
1148: PlotState parentState, PlotRenderingInfo info) {
1149: // adjust for insets...
1150: RectangleInsets insets = getInsets();
1151: insets.trim(area);
1152:
1153: if (info != null) {
1154: info.setPlotArea(area);
1155: info.setDataArea(area);
1156: }
1157:
1158: drawBackground(g2, area);
1159: drawOutline(g2, area);
1160:
1161: Shape savedClip = g2.getClip();
1162:
1163: g2.clip(area);
1164: Composite originalComposite = g2.getComposite();
1165: g2.setComposite(AlphaComposite.getInstance(
1166: AlphaComposite.SRC_OVER, getForegroundAlpha()));
1167:
1168: if (!DatasetUtilities.isEmptyOrNull(this .dataset)) {
1169: int seriesCount = 0, catCount = 0;
1170:
1171: if (this .dataExtractOrder == TableOrder.BY_ROW) {
1172: seriesCount = this .dataset.getRowCount();
1173: catCount = this .dataset.getColumnCount();
1174: } else {
1175: seriesCount = this .dataset.getColumnCount();
1176: catCount = this .dataset.getRowCount();
1177: }
1178:
1179: // ensure we have a maximum value to use on the axes
1180: if (this .maxValue == DEFAULT_MAX_VALUE)
1181: calculateMaxValue(seriesCount, catCount);
1182:
1183: // Next, setup the plot area
1184:
1185: // adjust the plot area by the interior spacing value
1186:
1187: double gapHorizontal = area.getWidth() * getInteriorGap();
1188: double gapVertical = area.getHeight() * getInteriorGap();
1189:
1190: double X = area.getX() + gapHorizontal / 2;
1191: double Y = area.getY() + gapVertical / 2;
1192: double W = area.getWidth() - gapHorizontal;
1193: double H = area.getHeight() - gapVertical;
1194:
1195: double headW = area.getWidth() * this .headPercent;
1196: double headH = area.getHeight() * this .headPercent;
1197:
1198: // make the chart area a square
1199: double min = Math.min(W, H) / 2;
1200: X = (X + X + W) / 2 - min;
1201: Y = (Y + Y + H) / 2 - min;
1202: W = 2 * min;
1203: H = 2 * min;
1204:
1205: Point2D centre = new Point2D.Double(X + W / 2, Y + H / 2);
1206: Rectangle2D radarArea = new Rectangle2D.Double(X, Y, W, H);
1207:
1208: // draw the axis and category label
1209: for (int cat = 0; cat < catCount; cat++) {
1210: double angle = getStartAngle()
1211: + (getDirection().getFactor() * cat * 360 / catCount);
1212:
1213: Point2D endPoint = getWebPoint(radarArea, angle, 1);
1214: // 1 = end of axis
1215: Line2D line = new Line2D.Double(centre, endPoint);
1216: g2.setPaint(this .axisLinePaint);
1217: g2.setStroke(this .axisLineStroke);
1218: g2.draw(line);
1219: drawLabel(g2, radarArea, 0.0, cat, angle,
1220: 360.0 / catCount);
1221: }
1222:
1223: // Now actually plot each of the series polygons..
1224: for (int series = 0; series < seriesCount; series++) {
1225: drawRadarPoly(g2, radarArea, centre, info, series,
1226: catCount, headH, headW);
1227: }
1228: } else {
1229: drawNoDataMessage(g2, area);
1230: }
1231: g2.setClip(savedClip);
1232: g2.setComposite(originalComposite);
1233: drawOutline(g2, area);
1234: }
1235:
1236: /**
1237: * loop through each of the series to get the maximum value
1238: * on each category axis
1239: *
1240: * @param seriesCount the number of series
1241: * @param catCount the number of categories
1242: */
1243: private void calculateMaxValue(int seriesCount, int catCount) {
1244: double v = 0;
1245: Number nV = null;
1246:
1247: for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {
1248: for (int catIndex = 0; catIndex < catCount; catIndex++) {
1249: nV = getPlotValue(seriesIndex, catIndex);
1250: if (nV != null) {
1251: v = nV.doubleValue();
1252: if (v > this .maxValue) {
1253: this .maxValue = v;
1254: }
1255: }
1256: }
1257: }
1258: }
1259:
1260: /**
1261: * Draws a radar plot polygon.
1262: *
1263: * @param g2 the graphics device.
1264: * @param plotArea the area we are plotting in (already adjusted).
1265: * @param centre the centre point of the radar axes
1266: * @param info chart rendering info.
1267: * @param series the series within the dataset we are plotting
1268: * @param catCount the number of categories per radar plot
1269: * @param headH the data point height
1270: * @param headW the data point width
1271: */
1272: protected void drawRadarPoly(Graphics2D g2, Rectangle2D plotArea,
1273: Point2D centre, PlotRenderingInfo info, int series,
1274: int catCount, double headH, double headW) {
1275:
1276: Polygon polygon = new Polygon();
1277:
1278: EntityCollection entities = null;
1279: if (info != null) {
1280: entities = info.getOwner().getEntityCollection();
1281: }
1282:
1283: // plot the data...
1284: for (int cat = 0; cat < catCount; cat++) {
1285:
1286: Number dataValue = getPlotValue(series, cat);
1287:
1288: if (dataValue != null) {
1289: double value = dataValue.doubleValue();
1290:
1291: if (value >= 0) { // draw the polygon series...
1292:
1293: // Finds our starting angle from the centre for this axis
1294:
1295: double angle = getStartAngle()
1296: + (getDirection().getFactor() * cat * 360 / catCount);
1297:
1298: // The following angle calc will ensure there isn't a top
1299: // vertical axis - this may be useful if you don't want any
1300: // given criteria to 'appear' move important than the
1301: // others..
1302: // + (getDirection().getFactor()
1303: // * (cat + 0.5) * 360 / catCount);
1304:
1305: // find the point at the appropriate distance end point
1306: // along the axis/angle identified above and add it to the
1307: // polygon
1308:
1309: Point2D point = getWebPoint(plotArea, angle, value
1310: / this .maxValue);
1311: polygon.addPoint((int) point.getX(), (int) point
1312: .getY());
1313:
1314: // put an elipse at the point being plotted..
1315:
1316: Paint paint = getSeriesPaint(series);
1317: Paint outlinePaint = getSeriesOutlinePaint(series);
1318: Stroke outlineStroke = getSeriesOutlineStroke(series);
1319:
1320: Ellipse2D head = new Ellipse2D.Double(point.getX()
1321: - headW / 2, point.getY() - headH / 2,
1322: headW, headH);
1323: g2.setPaint(paint);
1324: g2.fill(head);
1325: g2.setStroke(outlineStroke);
1326: g2.setPaint(outlinePaint);
1327: g2.draw(head);
1328:
1329: if (entities != null) {
1330: String tip = null;
1331: if (this .toolTipGenerator != null) {
1332: tip = this .toolTipGenerator
1333: .generateToolTip(this .dataset,
1334: series, cat);
1335: }
1336:
1337: String url = null;
1338: if (this .urlGenerator != null) {
1339: url = this .urlGenerator.generateURL(
1340: this .dataset, series, cat);
1341: }
1342:
1343: Shape area = new Rectangle(
1344: (int) (point.getX() - headW),
1345: (int) (point.getY() - headH),
1346: (int) (headW * 2), (int) (headH * 2));
1347: CategoryItemEntity entity = new CategoryItemEntity(
1348: area, tip, url, this .dataset,
1349: this .dataset.getRowKey(series),
1350: this .dataset.getColumnKey(cat));
1351: entities.add(entity);
1352: }
1353:
1354: }
1355: }
1356: }
1357: // Plot the polygon
1358:
1359: Paint paint = getSeriesPaint(series);
1360: g2.setPaint(paint);
1361: g2.setStroke(getSeriesOutlineStroke(series));
1362: g2.draw(polygon);
1363:
1364: // Lastly, fill the web polygon if this is required
1365:
1366: if (this .webFilled) {
1367: g2.setComposite(AlphaComposite.getInstance(
1368: AlphaComposite.SRC_OVER, 0.1f));
1369: g2.fill(polygon);
1370: g2.setComposite(AlphaComposite.getInstance(
1371: AlphaComposite.SRC_OVER, getForegroundAlpha()));
1372: }
1373: }
1374:
1375: /**
1376: * Returns the value to be plotted at the interseries of the
1377: * series and the category. This allows us to plot
1378: * <code>BY_ROW</code> or <code>BY_COLUMN</code> which basically is just
1379: * reversing the definition of the categories and data series being
1380: * plotted.
1381: *
1382: * @param series the series to be plotted.
1383: * @param cat the category within the series to be plotted.
1384: *
1385: * @return The value to be plotted (possibly <code>null</code>).
1386: *
1387: * @see #getDataExtractOrder()
1388: */
1389: protected Number getPlotValue(int series, int cat) {
1390: Number value = null;
1391: if (this .dataExtractOrder == TableOrder.BY_ROW) {
1392: value = this .dataset.getValue(series, cat);
1393: } else if (this .dataExtractOrder == TableOrder.BY_COLUMN) {
1394: value = this .dataset.getValue(cat, series);
1395: }
1396: return value;
1397: }
1398:
1399: /**
1400: * Draws the label for one axis.
1401: *
1402: * @param g2 the graphics device.
1403: * @param plotArea the plot area
1404: * @param value the value of the label (ignored).
1405: * @param cat the category (zero-based index).
1406: * @param startAngle the starting angle.
1407: * @param extent the extent of the arc.
1408: */
1409: protected void drawLabel(Graphics2D g2, Rectangle2D plotArea,
1410: double value, int cat, double startAngle, double extent) {
1411: FontRenderContext frc = g2.getFontRenderContext();
1412:
1413: String label = null;
1414: if (this .dataExtractOrder == TableOrder.BY_ROW) {
1415: // if series are in rows, then the categories are the column keys
1416: label = this .labelGenerator.generateColumnLabel(
1417: this .dataset, cat);
1418: } else {
1419: // if series are in columns, then the categories are the row keys
1420: label = this .labelGenerator.generateRowLabel(this .dataset,
1421: cat);
1422: }
1423:
1424: Rectangle2D labelBounds = getLabelFont().getStringBounds(label,
1425: frc);
1426: LineMetrics lm = getLabelFont().getLineMetrics(label, frc);
1427: double ascent = lm.getAscent();
1428:
1429: Point2D labelLocation = calculateLabelLocation(labelBounds,
1430: ascent, plotArea, startAngle);
1431:
1432: Composite saveComposite = g2.getComposite();
1433:
1434: g2.setComposite(AlphaComposite.getInstance(
1435: AlphaComposite.SRC_OVER, 1.0f));
1436: g2.setPaint(getLabelPaint());
1437: g2.setFont(getLabelFont());
1438: g2.drawString(label, (float) labelLocation.getX(),
1439: (float) labelLocation.getY());
1440: g2.setComposite(saveComposite);
1441: }
1442:
1443: /**
1444: * Returns the location for a label
1445: *
1446: * @param labelBounds the label bounds.
1447: * @param ascent the ascent (height of font).
1448: * @param plotArea the plot area
1449: * @param startAngle the start angle for the pie series.
1450: *
1451: * @return The location for a label.
1452: */
1453: protected Point2D calculateLabelLocation(Rectangle2D labelBounds,
1454: double ascent, Rectangle2D plotArea, double startAngle) {
1455: Arc2D arc1 = new Arc2D.Double(plotArea, startAngle, 0,
1456: Arc2D.OPEN);
1457: Point2D point1 = arc1.getEndPoint();
1458:
1459: double deltaX = -(point1.getX() - plotArea.getCenterX())
1460: * this .axisLabelGap;
1461: double deltaY = -(point1.getY() - plotArea.getCenterY())
1462: * this .axisLabelGap;
1463:
1464: double labelX = point1.getX() - deltaX;
1465: double labelY = point1.getY() - deltaY;
1466:
1467: if (labelX < plotArea.getCenterX()) {
1468: labelX -= labelBounds.getWidth();
1469: }
1470:
1471: if (labelX == plotArea.getCenterX()) {
1472: labelX -= labelBounds.getWidth() / 2;
1473: }
1474:
1475: if (labelY > plotArea.getCenterY()) {
1476: labelY += ascent;
1477: }
1478:
1479: return new Point2D.Double(labelX, labelY);
1480: }
1481:
1482: /**
1483: * Tests this plot for equality with an arbitrary object.
1484: *
1485: * @param obj the object (<code>null</code> permitted).
1486: *
1487: * @return A boolean.
1488: */
1489: public boolean equals(Object obj) {
1490: if (obj == this ) {
1491: return true;
1492: }
1493: if (!(obj instanceof SpiderWebPlot)) {
1494: return false;
1495: }
1496: if (!super .equals(obj)) {
1497: return false;
1498: }
1499: SpiderWebPlot that = (SpiderWebPlot) obj;
1500: if (!this .dataExtractOrder.equals(that.dataExtractOrder)) {
1501: return false;
1502: }
1503: if (this .headPercent != that.headPercent) {
1504: return false;
1505: }
1506: if (this .interiorGap != that.interiorGap) {
1507: return false;
1508: }
1509: if (this .startAngle != that.startAngle) {
1510: return false;
1511: }
1512: if (!this .direction.equals(that.direction)) {
1513: return false;
1514: }
1515: if (this .maxValue != that.maxValue) {
1516: return false;
1517: }
1518: if (this .webFilled != that.webFilled) {
1519: return false;
1520: }
1521: if (this .axisLabelGap != that.axisLabelGap) {
1522: return false;
1523: }
1524: if (!PaintUtilities.equal(this .axisLinePaint,
1525: that.axisLinePaint)) {
1526: return false;
1527: }
1528: if (!this .axisLineStroke.equals(that.axisLineStroke)) {
1529: return false;
1530: }
1531: if (!ShapeUtilities.equal(this .legendItemShape,
1532: that.legendItemShape)) {
1533: return false;
1534: }
1535: if (!PaintUtilities.equal(this .seriesPaint, that.seriesPaint)) {
1536: return false;
1537: }
1538: if (!this .seriesPaintList.equals(that.seriesPaintList)) {
1539: return false;
1540: }
1541: if (!PaintUtilities.equal(this .baseSeriesPaint,
1542: that.baseSeriesPaint)) {
1543: return false;
1544: }
1545: if (!PaintUtilities.equal(this .seriesOutlinePaint,
1546: that.seriesOutlinePaint)) {
1547: return false;
1548: }
1549: if (!this .seriesOutlinePaintList
1550: .equals(that.seriesOutlinePaintList)) {
1551: return false;
1552: }
1553: if (!PaintUtilities.equal(this .baseSeriesOutlinePaint,
1554: that.baseSeriesOutlinePaint)) {
1555: return false;
1556: }
1557: if (!ObjectUtilities.equal(this .seriesOutlineStroke,
1558: that.seriesOutlineStroke)) {
1559: return false;
1560: }
1561: if (!this .seriesOutlineStrokeList
1562: .equals(that.seriesOutlineStrokeList)) {
1563: return false;
1564: }
1565: if (!this .baseSeriesOutlineStroke
1566: .equals(that.baseSeriesOutlineStroke)) {
1567: return false;
1568: }
1569: if (!this .labelFont.equals(that.labelFont)) {
1570: return false;
1571: }
1572: if (!PaintUtilities.equal(this .labelPaint, that.labelPaint)) {
1573: return false;
1574: }
1575: if (!this .labelGenerator.equals(that.labelGenerator)) {
1576: return false;
1577: }
1578: if (!ObjectUtilities.equal(this .toolTipGenerator,
1579: that.toolTipGenerator)) {
1580: return false;
1581: }
1582: if (!ObjectUtilities
1583: .equal(this .urlGenerator, that.urlGenerator)) {
1584: return false;
1585: }
1586: return true;
1587: }
1588:
1589: /**
1590: * Returns a clone of this plot.
1591: *
1592: * @return A clone of this plot.
1593: *
1594: * @throws CloneNotSupportedException if the plot cannot be cloned for
1595: * any reason.
1596: */
1597: public Object clone() throws CloneNotSupportedException {
1598: SpiderWebPlot clone = (SpiderWebPlot) super .clone();
1599: clone.legendItemShape = ShapeUtilities
1600: .clone(this .legendItemShape);
1601: clone.seriesPaintList = (PaintList) this .seriesPaintList
1602: .clone();
1603: clone.seriesOutlinePaintList = (PaintList) this .seriesOutlinePaintList
1604: .clone();
1605: clone.seriesOutlineStrokeList = (StrokeList) this .seriesOutlineStrokeList
1606: .clone();
1607: return clone;
1608: }
1609:
1610: /**
1611: * Provides serialization support.
1612: *
1613: * @param stream the output stream.
1614: *
1615: * @throws IOException if there is an I/O error.
1616: */
1617: private void writeObject(ObjectOutputStream stream)
1618: throws IOException {
1619: stream.defaultWriteObject();
1620:
1621: SerialUtilities.writeShape(this .legendItemShape, stream);
1622: SerialUtilities.writePaint(this .seriesPaint, stream);
1623: SerialUtilities.writePaint(this .baseSeriesPaint, stream);
1624: SerialUtilities.writePaint(this .seriesOutlinePaint, stream);
1625: SerialUtilities.writePaint(this .baseSeriesOutlinePaint, stream);
1626: SerialUtilities.writeStroke(this .seriesOutlineStroke, stream);
1627: SerialUtilities.writeStroke(this .baseSeriesOutlineStroke,
1628: stream);
1629: SerialUtilities.writePaint(this .labelPaint, stream);
1630: SerialUtilities.writePaint(this .axisLinePaint, stream);
1631: SerialUtilities.writeStroke(this .axisLineStroke, stream);
1632: }
1633:
1634: /**
1635: * Provides serialization support.
1636: *
1637: * @param stream the input stream.
1638: *
1639: * @throws IOException if there is an I/O error.
1640: * @throws ClassNotFoundException if there is a classpath problem.
1641: */
1642: private void readObject(ObjectInputStream stream)
1643: throws IOException, ClassNotFoundException {
1644: stream.defaultReadObject();
1645:
1646: this.legendItemShape = SerialUtilities.readShape(stream);
1647: this.seriesPaint = SerialUtilities.readPaint(stream);
1648: this.baseSeriesPaint = SerialUtilities.readPaint(stream);
1649: this.seriesOutlinePaint = SerialUtilities.readPaint(stream);
1650: this.baseSeriesOutlinePaint = SerialUtilities.readPaint(stream);
1651: this.seriesOutlineStroke = SerialUtilities.readStroke(stream);
1652: this.baseSeriesOutlineStroke = SerialUtilities
1653: .readStroke(stream);
1654: this.labelPaint = SerialUtilities.readPaint(stream);
1655: this.axisLinePaint = SerialUtilities.readPaint(stream);
1656: this.axisLineStroke = SerialUtilities.readStroke(stream);
1657: if (this.dataset != null) {
1658: this.dataset.addChangeListener(this);
1659: }
1660: }
1661:
1662: }
|