Source for org.jfree.chart.renderer.xy.XYAreaRenderer2

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------------
  28:  * XYAreaRenderer2.java
  29:  * --------------------
  30:  * (C) Copyright 2004-2007, by Hari and Contributors.
  31:  *
  32:  * Original Author:  Hari (ourhari@hotmail.com);
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Richard Atkinson;
  35:  *                   Christian W. Zuckschwerdt;
  36:  *
  37:  * $Id: XYAreaRenderer2.java,v 1.12.2.7 2007/02/06 16:29:11 mungady Exp $
  38:  *
  39:  * Changes:
  40:  * --------
  41:  * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the 
  42:  *               StandardXYItemRenderer class (DG);
  43:  * 09-Apr-2002 : Removed the translated zero from the drawItem method - 
  44:  *               overridden the initialise() method to calculate it (DG);
  45:  * 30-May-2002 : Added tool tip generator to constructor to match super 
  46:  *               class (DG);
  47:  * 25-Jun-2002 : Removed unnecessary local variable (DG);
  48:  * 05-Aug-2002 : Small modification to drawItem method to support URLs for 
  49:  *               HTML image maps (RA);
  50:  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  51:  * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
  52:  * 25-Mar-2003 : Implemented Serializable (DG);
  53:  * 01-May-2003 : Modified drawItem() method signature (DG);
  54:  * 27-Jul-2003 : Made line and polygon properties protected rather than 
  55:  *               private (RA);
  56:  * 30-Jul-2003 : Modified entity constructor (CZ);
  57:  * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  58:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  59:  * 07-Oct-2003 : Added renderer state (DG);
  60:  * 08-Dec-2003 : Modified hotspot for chart entity (DG);
  61:  * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste 
  62:  *               overriding easier.  Also moved state class into this 
  63:  *               class (DG);
  64:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
  65:  *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
  66:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  67:  *               getYValue() (DG);
  68:  * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  69:  * 19-Jan-2005 : Now accesses only primitives from the dataset (DG);
  70:  * 21-Mar-2005 : Override getLegendItem() (DG);
  71:  * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
  72:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  73:  * 30-Nov-2006 : Fixed equals() and clone() implementations (DG);
  74:  * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
  75:  *
  76:  */
  77: 
  78: package org.jfree.chart.renderer.xy;
  79: 
  80: import java.awt.Graphics2D;
  81: import java.awt.Paint;
  82: import java.awt.Polygon;
  83: import java.awt.Shape;
  84: import java.awt.Stroke;
  85: import java.awt.geom.GeneralPath;
  86: import java.awt.geom.Rectangle2D;
  87: import java.io.IOException;
  88: import java.io.ObjectInputStream;
  89: import java.io.ObjectOutputStream;
  90: import java.io.Serializable;
  91: 
  92: import org.jfree.chart.LegendItem;
  93: import org.jfree.chart.axis.ValueAxis;
  94: import org.jfree.chart.entity.EntityCollection;
  95: import org.jfree.chart.entity.XYItemEntity;
  96: import org.jfree.chart.event.RendererChangeEvent;
  97: import org.jfree.chart.labels.XYSeriesLabelGenerator;
  98: import org.jfree.chart.labels.XYToolTipGenerator;
  99: import org.jfree.chart.plot.CrosshairState;
 100: import org.jfree.chart.plot.PlotOrientation;
 101: import org.jfree.chart.plot.PlotRenderingInfo;
 102: import org.jfree.chart.plot.XYPlot;
 103: import org.jfree.chart.urls.XYURLGenerator;
 104: import org.jfree.data.xy.XYDataset;
 105: import org.jfree.io.SerialUtilities;
 106: import org.jfree.util.PublicCloneable;
 107: import org.jfree.util.ShapeUtilities;
 108: 
 109: /**
 110:  * Area item renderer for an {@link XYPlot}.  
 111:  */
 112: public class XYAreaRenderer2 extends AbstractXYItemRenderer 
 113:                              implements XYItemRenderer, 
 114:                                         Cloneable,
 115:                                         PublicCloneable,
 116:                                         Serializable {
 117: 
 118:     /** For serialization. */
 119:     private static final long serialVersionUID = -7378069681579984133L;
 120: 
 121:     /** A flag that controls whether or not the outline is shown. */
 122:     private boolean showOutline;
 123: 
 124:     /** 
 125:      * The shape used to represent an area in each legend item (this should 
 126:      * never be <code>null</code>). 
 127:      */
 128:     private transient Shape legendArea;
 129: 
 130:     /**
 131:      * Constructs a new renderer.
 132:      */
 133:     public XYAreaRenderer2() {
 134:         this(null, null);
 135:     }
 136: 
 137:     /**
 138:      * Constructs a new renderer.
 139:      *
 140:      * @param labelGenerator  the tool tip generator to use.  <code>null</code> 
 141:      *                        is none.
 142:      * @param urlGenerator  the URL generator (null permitted).
 143:      */
 144:     public XYAreaRenderer2(XYToolTipGenerator labelGenerator, 
 145:                            XYURLGenerator urlGenerator) {
 146:         super();
 147:         this.showOutline = false;
 148:         setBaseToolTipGenerator(labelGenerator);
 149:         setURLGenerator(urlGenerator);
 150:         GeneralPath area = new GeneralPath();
 151:         area.moveTo(0.0f, -4.0f);
 152:         area.lineTo(3.0f, -2.0f);
 153:         area.lineTo(4.0f, 4.0f);
 154:         area.lineTo(-4.0f, 4.0f);
 155:         area.lineTo(-3.0f, -2.0f);
 156:         area.closePath();
 157:         this.legendArea = area;
 158:     }
 159: 
 160:     /**
 161:      * Returns a flag that controls whether or not outlines of the areas are 
 162:      * drawn.
 163:      *
 164:      * @return The flag.
 165:      * 
 166:      * @see #setOutline(boolean)
 167:      */
 168:     public boolean isOutline() {
 169:         return this.showOutline;
 170:     }
 171: 
 172:     /**
 173:      * Sets a flag that controls whether or not outlines of the areas are 
 174:      * drawn, and sends a {@link RendererChangeEvent} to all registered 
 175:      * listeners.
 176:      *
 177:      * @param show  the flag.
 178:      * 
 179:      * @see #isOutline()
 180:      */
 181:     public void setOutline(boolean show) {
 182:         this.showOutline = show;
 183:         notifyListeners(new RendererChangeEvent(this));
 184:     }
 185: 
 186:     /**
 187:      * This method should not be used.
 188:      *
 189:      * @return <code>false</code> always.
 190:      * 
 191:      * @deprecated This method was included in the API by mistake and serves
 192:      *     no useful purpose.  It has always returned <code>false</code>.
 193:      *   
 194:      */
 195:     public boolean getPlotLines() {
 196:         return false;
 197:     }
 198: 
 199:     /**
 200:      * Returns the shape used to represent an area in the legend.
 201:      * 
 202:      * @return The legend area (never <code>null</code>).
 203:      * 
 204:      * @see #setLegendArea(Shape)
 205:      */
 206:     public Shape getLegendArea() {
 207:         return this.legendArea;   
 208:     }
 209:     
 210:     /**
 211:      * Sets the shape used as an area in each legend item and sends a 
 212:      * {@link RendererChangeEvent} to all registered listeners.
 213:      * 
 214:      * @param area  the area (<code>null</code> not permitted).
 215:      * 
 216:      * @see #getLegendArea()
 217:      */
 218:     public void setLegendArea(Shape area) {
 219:         if (area == null) {
 220:             throw new IllegalArgumentException("Null 'area' argument.");   
 221:         }
 222:         this.legendArea = area;
 223:         notifyListeners(new RendererChangeEvent(this));
 224:     }
 225: 
 226:     /**
 227:      * Returns a default legend item for the specified series.  Subclasses 
 228:      * should override this method to generate customised items.
 229:      *
 230:      * @param datasetIndex  the dataset index (zero-based).
 231:      * @param series  the series index (zero-based).
 232:      *
 233:      * @return A legend item for the series.
 234:      */
 235:     public LegendItem getLegendItem(int datasetIndex, int series) {
 236:         LegendItem result = null;
 237:         XYPlot xyplot = getPlot();
 238:         if (xyplot != null) {
 239:             XYDataset dataset = xyplot.getDataset(datasetIndex);
 240:             if (dataset != null) {
 241:                 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
 242:                 String label = lg.generateLabel(dataset, series);
 243:                 String description = label;
 244:                 String toolTipText = null;
 245:                 if (getLegendItemToolTipGenerator() != null) {
 246:                     toolTipText = getLegendItemToolTipGenerator().generateLabel(
 247:                             dataset, series);
 248:                 }
 249:                 String urlText = null;
 250:                 if (getLegendItemURLGenerator() != null) {
 251:                     urlText = getLegendItemURLGenerator().generateLabel(
 252:                             dataset, series);
 253:                 }
 254:                 Paint paint = getSeriesPaint(series);
 255:                 result = new LegendItem(label, description, toolTipText, 
 256:                         urlText, this.legendArea, paint);
 257:             }
 258:         }
 259:         return result;
 260:     }
 261:     
 262:     /**
 263:      * Draws the visual representation of a single data item.
 264:      *
 265:      * @param g2  the graphics device.
 266:      * @param state  the renderer state.
 267:      * @param dataArea  the area within which the data is being drawn.
 268:      * @param info  collects information about the drawing.
 269:      * @param plot  the plot (can be used to obtain standard color 
 270:      *              information etc).
 271:      * @param domainAxis  the domain axis.
 272:      * @param rangeAxis  the range axis.
 273:      * @param dataset  the dataset.
 274:      * @param series  the series index (zero-based).
 275:      * @param item  the item index (zero-based).
 276:      * @param crosshairState  crosshair information for the plot 
 277:      *                        (<code>null</code> permitted).
 278:      * @param pass  the pass index.
 279:      */
 280:     public void drawItem(Graphics2D g2,
 281:                          XYItemRendererState state,
 282:                          Rectangle2D dataArea,
 283:                          PlotRenderingInfo info,
 284:                          XYPlot plot,
 285:                          ValueAxis domainAxis,
 286:                          ValueAxis rangeAxis,
 287:                          XYDataset dataset,
 288:                          int series,
 289:                          int item,
 290:                          CrosshairState crosshairState,
 291:                          int pass) {
 292:         
 293:         if (!getItemVisible(series, item)) {
 294:             return;   
 295:         }
 296:         // get the data point...
 297:         double x1 = dataset.getXValue(series, item);
 298:         double y1 = dataset.getYValue(series, item);
 299:         if (Double.isNaN(y1)) {
 300:             y1 = 0.0;
 301:         }
 302:         
 303:         double transX1 = domainAxis.valueToJava2D(x1, dataArea, 
 304:                 plot.getDomainAxisEdge());
 305:         double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 
 306:                 plot.getRangeAxisEdge());
 307:         
 308:         // get the previous point and the next point so we can calculate a 
 309:         // "hot spot" for the area (used by the chart entity)...
 310:         double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
 311:         double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
 312:         if (Double.isNaN(y0)) {
 313:             y0 = 0.0;
 314:         }
 315:         double transX0 = domainAxis.valueToJava2D(x0, dataArea, 
 316:                 plot.getDomainAxisEdge());
 317:         double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
 318:                 plot.getRangeAxisEdge());
 319:         
 320:         int itemCount = dataset.getItemCount(series);
 321:         double x2 = dataset.getXValue(series, Math.min(item + 1, 
 322:                 itemCount - 1));
 323:         double y2 = dataset.getYValue(series, Math.min(item + 1, 
 324:                 itemCount - 1));
 325:         if (Double.isNaN(y2)) {
 326:             y2 = 0.0;
 327:         }
 328:         double transX2 = domainAxis.valueToJava2D(x2, dataArea, 
 329:                 plot.getDomainAxisEdge());
 330:         double transY2 = rangeAxis.valueToJava2D(y2, dataArea, 
 331:                 plot.getRangeAxisEdge());
 332:         
 333:         double transZero = rangeAxis.valueToJava2D(0.0, dataArea, 
 334:                 plot.getRangeAxisEdge());
 335:         Polygon hotspot = null;
 336:         if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
 337:             hotspot = new Polygon();
 338:             hotspot.addPoint((int) transZero, 
 339:                     (int) ((transX0 + transX1) / 2.0));
 340:             hotspot.addPoint((int) ((transY0 + transY1) / 2.0), 
 341:                     (int) ((transX0 + transX1) / 2.0));
 342:             hotspot.addPoint((int) transY1, (int) transX1);
 343:             hotspot.addPoint((int) ((transY1 + transY2) / 2.0), 
 344:                     (int) ((transX1 + transX2) / 2.0));
 345:             hotspot.addPoint((int) transZero, 
 346:                     (int) ((transX1 + transX2) / 2.0));
 347:         }
 348:         else {  // vertical orientation
 349:             hotspot = new Polygon();
 350:             hotspot.addPoint((int) ((transX0 + transX1) / 2.0), 
 351:                     (int) transZero);
 352:             hotspot.addPoint((int) ((transX0 + transX1) / 2.0), 
 353:                     (int) ((transY0 + transY1) / 2.0));
 354:             hotspot.addPoint((int) transX1, (int) transY1);
 355:             hotspot.addPoint((int) ((transX1 + transX2) / 2.0), 
 356:                     (int) ((transY1 + transY2) / 2.0));
 357:             hotspot.addPoint((int) ((transX1 + transX2) / 2.0), 
 358:                     (int) transZero);
 359:         }
 360:                 
 361:         PlotOrientation orientation = plot.getOrientation();
 362:         Paint paint = getItemPaint(series, item);
 363:         Stroke stroke = getItemStroke(series, item);
 364:         g2.setPaint(paint);
 365:         g2.setStroke(stroke);
 366: 
 367:         if (getPlotLines()) {
 368:             if (item > 0) {
 369:                 if (plot.getOrientation() == PlotOrientation.VERTICAL) {
 370:                     state.workingLine.setLine(transX0, transY0, transX1, 
 371:                             transY1);
 372:                 }
 373:                 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
 374:                     state.workingLine.setLine(transY0, transX0, transY1, 
 375:                             transX1);
 376:                 }
 377:                 g2.draw(state.workingLine);
 378:             }
 379:         }
 380: 
 381:         // Check if the item is the last item for the series.
 382:         // and number of items > 0.  We can't draw an area for a single point.
 383:         g2.fill(hotspot);
 384: 
 385:         // draw an outline around the Area.
 386:         if (isOutline()) {
 387:             g2.setStroke(getSeriesOutlineStroke(series));
 388:             g2.setPaint(getSeriesOutlinePaint(series));
 389:             g2.draw(hotspot);
 390:         }
 391:         int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
 392:         int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
 393:         updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 
 394:                 rangeAxisIndex, transX1, transY1, orientation);
 395:         
 396:         // collect entity and tool tip information...
 397:         if (state.getInfo() != null) {
 398:             EntityCollection entities = state.getEntityCollection();
 399:             if (entities != null && hotspot != null) {
 400:                 String tip = null;
 401:                 XYToolTipGenerator generator = getToolTipGenerator(
 402:                     series, item
 403:                 );
 404:                 if (generator != null) {
 405:                     tip = generator.generateToolTip(dataset, series, item);
 406:                 }
 407:                 String url = null;
 408:                 if (getURLGenerator() != null) {
 409:                     url = getURLGenerator().generateURL(dataset, series, item);
 410:                 }
 411:                 XYItemEntity entity = new XYItemEntity(hotspot, dataset, 
 412:                         series, item, tip, url);
 413:                 entities.add(entity);
 414:             }
 415:         }
 416: 
 417:     }
 418: 
 419:     /**
 420:      * Tests this renderer for equality with an arbitrary object.
 421:      * 
 422:      * @param obj  the object (<code>null</code> not permitted).
 423:      * 
 424:      * @return A boolean.
 425:      */
 426:     public boolean equals(Object obj) {
 427:         if (obj == this) {    
 428:             return true;
 429:         }
 430:         if (!(obj instanceof XYAreaRenderer2)) {
 431:             return false;
 432:         }
 433:         XYAreaRenderer2 that = (XYAreaRenderer2) obj;
 434:         if (this.showOutline != that.showOutline) {
 435:             return false;
 436:         }
 437:         if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
 438:             return false;
 439:         }
 440:         return super.equals(obj);
 441:     }
 442:     
 443:     /**
 444:      * Returns a clone of the renderer.
 445:      * 
 446:      * @return A clone.
 447:      * 
 448:      * @throws CloneNotSupportedException  if the renderer cannot be cloned.
 449:      */
 450:     public Object clone() throws CloneNotSupportedException {
 451:         XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
 452:         clone.legendArea = ShapeUtilities.clone(this.legendArea);
 453:         return clone;
 454:     }
 455:     
 456:     /**
 457:      * Provides serialization support.
 458:      *
 459:      * @param stream  the input stream.
 460:      *
 461:      * @throws IOException  if there is an I/O error.
 462:      * @throws ClassNotFoundException  if there is a classpath problem.
 463:      */
 464:     private void readObject(ObjectInputStream stream) 
 465:             throws IOException, ClassNotFoundException {
 466:         stream.defaultReadObject();
 467:         this.legendArea = SerialUtilities.readShape(stream);
 468:     }
 469:     
 470:     /**
 471:      * Provides serialization support.
 472:      *
 473:      * @param stream  the output stream.
 474:      *
 475:      * @throws IOException  if there is an I/O error.
 476:      */
 477:     private void writeObject(ObjectOutputStream stream) throws IOException {
 478:         stream.defaultWriteObject();
 479:         SerialUtilities.writeShape(this.legendArea, stream);
 480:     }
 481: 
 482: }
 483: