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

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, 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:  * XYDifferenceRenderer.java
  29:  * -------------------------
  30:  * (C) Copyright 2003-2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Christian W. Zuckschwerdt;
  34:  *
  35:  * $Id: XYDifferenceRenderer.java,v 1.12.2.6 2005/12/10 21:51:02 mungady Exp $
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 30-Apr-2003 : Version 1 (DG);
  40:  * 30-Jul-2003 : Modified entity constructor (CZ);
  41:  * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  42:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  43:  * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
  44:  * 10-Feb-2004 : Added default constructor, setter methods and updated 
  45:  *               Javadocs (DG);
  46:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  47:  * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
  48:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  49:  *               getYValue() (DG);
  50:  * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
  51:  * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  52:  * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
  53:  * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
  54:  * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
  55:  * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
  56:  * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
  57:  *               get/setShapesVisible (DG);
  58:  * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
  59:  * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
  60:  * 
  61:  */
  62: 
  63: package org.jfree.chart.renderer.xy;
  64: 
  65: import java.awt.Color;
  66: import java.awt.Graphics2D;
  67: import java.awt.Paint;
  68: import java.awt.Shape;
  69: import java.awt.Stroke;
  70: import java.awt.geom.GeneralPath;
  71: import java.awt.geom.Line2D;
  72: import java.awt.geom.Rectangle2D;
  73: import java.io.IOException;
  74: import java.io.ObjectInputStream;
  75: import java.io.ObjectOutputStream;
  76: import java.io.Serializable;
  77: 
  78: import org.jfree.chart.LegendItem;
  79: import org.jfree.chart.axis.ValueAxis;
  80: import org.jfree.chart.entity.EntityCollection;
  81: import org.jfree.chart.entity.XYItemEntity;
  82: import org.jfree.chart.event.RendererChangeEvent;
  83: import org.jfree.chart.labels.XYToolTipGenerator;
  84: import org.jfree.chart.plot.CrosshairState;
  85: import org.jfree.chart.plot.PlotOrientation;
  86: import org.jfree.chart.plot.PlotRenderingInfo;
  87: import org.jfree.chart.plot.XYPlot;
  88: import org.jfree.data.xy.XYDataset;
  89: import org.jfree.io.SerialUtilities;
  90: import org.jfree.ui.RectangleEdge;
  91: import org.jfree.util.PaintUtilities;
  92: import org.jfree.util.PublicCloneable;
  93: import org.jfree.util.ShapeUtilities;
  94: 
  95: /**
  96:  * A renderer for an {@link XYPlot} that highlights the differences between two
  97:  * series.  The renderer expects a dataset that:
  98:  * <ul>
  99:  * <li>has exactly two series;</li>
 100:  * <li>each series has the same x-values;</li>
 101:  * <li>no <code>null</code> values;
 102:  * </ul>
 103:  */
 104: public class XYDifferenceRenderer extends AbstractXYItemRenderer 
 105:                                   implements XYItemRenderer, 
 106:                                              Cloneable,
 107:                                              PublicCloneable,
 108:                                              Serializable {
 109: 
 110:     /** For serialization. */
 111:     private static final long serialVersionUID = -8447915602375584857L;
 112:     
 113:     /** The paint used to highlight positive differences (y(0) > y(1)). */
 114:     private transient Paint positivePaint;
 115: 
 116:     /** The paint used to highlight negative differences (y(0) < y(1)). */
 117:     private transient Paint negativePaint;
 118: 
 119:     /** Display shapes at each point? */
 120:     private boolean shapesVisible;
 121:     
 122:     /** The shape to display in the legend item. */
 123:     private transient Shape legendLine;
 124: 
 125:     /**
 126:      * Creates a new renderer with default attributes.
 127:      */
 128:     public XYDifferenceRenderer() {
 129:         this(Color.green, Color.red, false);
 130:     }
 131:     
 132:     /**
 133:      * Creates a new renderer.
 134:      *
 135:      * @param positivePaint  the highlight color for positive differences 
 136:      *                       (<code>null</code> not permitted).
 137:      * @param negativePaint  the highlight color for negative differences 
 138:      *                       (<code>null</code> not permitted).
 139:      * @param shapes  draw shapes?
 140:      */
 141:     public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint, 
 142:                                 boolean shapes) {
 143:         if (positivePaint == null) {
 144:             throw new IllegalArgumentException(
 145:                     "Null 'positivePaint' argument.");
 146:         }
 147:         if (negativePaint == null) {
 148:             throw new IllegalArgumentException(
 149:                     "Null 'negativePaint' argument.");
 150:         }
 151:         this.positivePaint = positivePaint;
 152:         this.negativePaint = negativePaint;
 153:         this.shapesVisible = shapes;
 154:         this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
 155:     }
 156: 
 157:     /**
 158:      * Returns the paint used to highlight positive differences.
 159:      *
 160:      * @return The paint (never <code>null</code>).
 161:      */
 162:     public Paint getPositivePaint() {
 163:         return this.positivePaint;
 164:     }
 165: 
 166:     /**
 167:      * Sets the paint used to highlight positive differences.
 168:      * 
 169:      * @param paint  the paint (<code>null</code> not permitted).
 170:      */
 171:     public void setPositivePaint(Paint paint) {
 172:         if (paint == null) {
 173:             throw new IllegalArgumentException("Null 'paint' argument.");
 174:         }
 175:         this.positivePaint = paint;
 176:         notifyListeners(new RendererChangeEvent(this));
 177:     }
 178: 
 179:     /**
 180:      * Returns the paint used to highlight negative differences.
 181:      *
 182:      * @return The paint (never <code>null</code>).
 183:      */
 184:     public Paint getNegativePaint() {
 185:         return this.negativePaint;
 186:     }
 187:     
 188:     /**
 189:      * Sets the paint used to highlight negative differences.
 190:      * 
 191:      * @param paint  the paint (<code>null</code> not permitted).
 192:      */
 193:     public void setNegativePaint(Paint paint) {
 194:         if (paint == null) {
 195:             throw new IllegalArgumentException("Null 'paint' argument.");
 196:         }
 197:         this.negativePaint = paint;
 198:         notifyListeners(new RendererChangeEvent(this));
 199:     }
 200: 
 201:     /**
 202:      * Returns a flag that controls whether or not shapes are drawn for each 
 203:      * data value.
 204:      * 
 205:      * @return A boolean.
 206:      */
 207:     public boolean getShapesVisible() {
 208:         return this.shapesVisible;
 209:     }
 210: 
 211:     /**
 212:      * Sets a flag that controls whether or not shapes are drawn for each 
 213:      * data value.
 214:      * 
 215:      * @param flag  the flag.
 216:      */
 217:     public void setShapesVisible(boolean flag) {
 218:         this.shapesVisible = flag;
 219:         notifyListeners(new RendererChangeEvent(this));
 220:     }
 221:     
 222:     /**
 223:      * Returns the shape used to represent a line in the legend.
 224:      * 
 225:      * @return The legend line (never <code>null</code>).
 226:      */
 227:     public Shape getLegendLine() {
 228:         return this.legendLine;   
 229:     }
 230:     
 231:     /**
 232:      * Sets the shape used as a line in each legend item and sends a 
 233:      * {@link RendererChangeEvent} to all registered listeners.
 234:      * 
 235:      * @param line  the line (<code>null</code> not permitted).
 236:      */
 237:     public void setLegendLine(Shape line) {
 238:         if (line == null) {
 239:             throw new IllegalArgumentException("Null 'line' argument.");   
 240:         }
 241:         this.legendLine = line;
 242:         notifyListeners(new RendererChangeEvent(this));
 243:     }
 244: 
 245:     /**
 246:      * Initialises the renderer and returns a state object that should be 
 247:      * passed to subsequent calls to the drawItem() method.  This method will 
 248:      * be called before the first item is rendered, giving the renderer an 
 249:      * opportunity to initialise any state information it wants to maintain.  
 250:      * The renderer can do nothing if it chooses.
 251:      *
 252:      * @param g2  the graphics device.
 253:      * @param dataArea  the area inside the axes.
 254:      * @param plot  the plot.
 255:      * @param data  the data.
 256:      * @param info  an optional info collection object to return data back to 
 257:      *              the caller.
 258:      *
 259:      * @return A state object.
 260:      */
 261:     public XYItemRendererState initialise(Graphics2D g2,
 262:                                           Rectangle2D dataArea,
 263:                                           XYPlot plot,
 264:                                           XYDataset data,
 265:                                           PlotRenderingInfo info) {
 266: 
 267:         return super.initialise(g2, dataArea, plot, data, info);
 268: 
 269:     }
 270: 
 271:     /**
 272:      * Returns <code>2</code>, the number of passes required by the renderer.  
 273:      * The {@link XYPlot} will run through the dataset this number of times.
 274:      * 
 275:      * @return The number of passes required by the renderer.
 276:      */
 277:     public int getPassCount() {
 278:         return 2;
 279:     }
 280:     
 281:     /**
 282:      * Draws the visual representation of a single data item.
 283:      *
 284:      * @param g2  the graphics device.
 285:      * @param state  the renderer state.
 286:      * @param dataArea  the area within which the data is being drawn.
 287:      * @param info  collects information about the drawing.
 288:      * @param plot  the plot (can be used to obtain standard color 
 289:      *              information etc).
 290:      * @param domainAxis  the domain (horizontal) axis.
 291:      * @param rangeAxis  the range (vertical) axis.
 292:      * @param dataset  the dataset.
 293:      * @param series  the series index (zero-based).
 294:      * @param item  the item index (zero-based).
 295:      * @param crosshairState  crosshair information for the plot 
 296:      *                        (<code>null</code> permitted).
 297:      * @param pass  the pass index.
 298:      */
 299:     public void drawItem(Graphics2D g2,
 300:                          XYItemRendererState state,
 301:                          Rectangle2D dataArea,
 302:                          PlotRenderingInfo info,
 303:                          XYPlot plot,
 304:                          ValueAxis domainAxis,
 305:                          ValueAxis rangeAxis,
 306:                          XYDataset dataset,
 307:                          int series,
 308:                          int item,
 309:                          CrosshairState crosshairState,
 310:                          int pass) {
 311: 
 312:         if (pass == 0) {
 313:             drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis, 
 314:                     dataset, series, item, crosshairState);
 315:         }
 316:         else if (pass == 1) {
 317:             drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis, 
 318:                     dataset, series, item, crosshairState);
 319:         }
 320: 
 321:     }
 322: 
 323:     /**
 324:      * Draws the visual representation of a single data item, first pass.
 325:      *
 326:      * @param g2  the graphics device.
 327:      * @param dataArea  the area within which the data is being drawn.
 328:      * @param info  collects information about the drawing.
 329:      * @param plot  the plot (can be used to obtain standard color 
 330:      *              information etc).
 331:      * @param domainAxis  the domain (horizontal) axis.
 332:      * @param rangeAxis  the range (vertical) axis.
 333:      * @param dataset  the dataset.
 334:      * @param series  the series index (zero-based).
 335:      * @param item  the item index (zero-based).
 336:      * @param crosshairState  crosshair information for the plot 
 337:      *                        (<code>null</code> permitted).
 338:      */
 339:     protected void drawItemPass0(Graphics2D g2,
 340:                                  Rectangle2D dataArea,
 341:                                  PlotRenderingInfo info,
 342:                                  XYPlot plot,
 343:                                  ValueAxis domainAxis,
 344:                                  ValueAxis rangeAxis,
 345:                                  XYDataset dataset,
 346:                                  int series,
 347:                                  int item,
 348:                                  CrosshairState crosshairState) {
 349: 
 350:         if (series == 0) {
 351: 
 352:             PlotOrientation orientation = plot.getOrientation();
 353:             RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
 354:             RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
 355:             
 356:             double y0 = dataset.getYValue(0, item);
 357:             double x1 = dataset.getXValue(1, item);
 358:             double y1 = dataset.getYValue(1, item);
 359: 
 360:             double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
 361:                     rangeAxisLocation);
 362:             double transX1 = domainAxis.valueToJava2D(x1, dataArea, 
 363:                     domainAxisLocation);
 364:             double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 
 365:                     rangeAxisLocation);
 366: 
 367:             if (item > 0) {
 368:                 double prevx0 = dataset.getXValue(0, item - 1);
 369:                 double prevy0 = dataset.getYValue(0, item - 1);
 370:                 double prevy1 = dataset.getYValue(1, item - 1);
 371: 
 372:                 double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea, 
 373:                         domainAxisLocation);
 374:                 double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea, 
 375:                         rangeAxisLocation);
 376:                 double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea, 
 377:                         rangeAxisLocation);
 378: 
 379:                 Shape positive = getPositiveArea((float) prevtransX0, 
 380:                         (float) prevtransY0, (float) prevtransY1,
 381:                         (float) transX1, (float) transY0, (float) transY1,
 382:                         orientation);
 383:                 if (positive != null) {
 384:                     g2.setPaint(getPositivePaint());
 385:                     g2.fill(positive);
 386:                 }
 387: 
 388:                 Shape negative = getNegativeArea((float) prevtransX0, 
 389:                         (float) prevtransY0, (float) prevtransY1,
 390:                         (float) transX1, (float) transY0, (float) transY1,
 391:                         orientation);
 392: 
 393:                 if (negative != null) {
 394:                     g2.setPaint(getNegativePaint());
 395:                     g2.fill(negative);
 396:                 }
 397:             }
 398:         }
 399: 
 400:     }
 401: 
 402:     /**
 403:      * Draws the visual representation of a single data item, second pass.  In 
 404:      * the second pass, the renderer draws the lines and shapes for the 
 405:      * individual points in the two series.
 406:      *
 407:      * @param g2  the graphics device.
 408:      * @param dataArea  the area within which the data is being drawn.
 409:      * @param info  collects information about the drawing.
 410:      * @param plot  the plot (can be used to obtain standard color information 
 411:      *              etc).
 412:      * @param domainAxis  the domain (horizontal) axis.
 413:      * @param rangeAxis  the range (vertical) axis.
 414:      * @param dataset  the dataset.
 415:      * @param series  the series index (zero-based).
 416:      * @param item  the item index (zero-based).
 417:      * @param crosshairState  crosshair information for the plot 
 418:      *                        (<code>null</code> permitted).
 419:      */
 420:     protected void drawItemPass1(Graphics2D g2,
 421:                                  Rectangle2D dataArea,
 422:                                  PlotRenderingInfo info,
 423:                                  XYPlot plot,
 424:                                  ValueAxis domainAxis,
 425:                                  ValueAxis rangeAxis,
 426:                                  XYDataset dataset,
 427:                                  int series,
 428:                                  int item,
 429:                                  CrosshairState crosshairState) {
 430: 
 431:         Shape entityArea = null;
 432:         EntityCollection entities = null;
 433:         if (info != null) {
 434:             entities = info.getOwner().getEntityCollection();
 435:         }
 436: 
 437:         Paint seriesPaint = getItemPaint(series, item);
 438:         Stroke seriesStroke = getItemStroke(series, item);
 439:         g2.setPaint(seriesPaint);
 440:         g2.setStroke(seriesStroke);
 441: 
 442:         if (series == 0) {
 443: 
 444:             PlotOrientation orientation = plot.getOrientation(); 
 445:             RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
 446:             RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
 447: 
 448:             double x0 = dataset.getXValue(0, item);
 449:             double y0 = dataset.getYValue(0, item);
 450:             double x1 = dataset.getXValue(1, item);
 451:             double y1 = dataset.getYValue(1, item);
 452: 
 453:             double transX0 = domainAxis.valueToJava2D(x0, dataArea, 
 454:                     domainAxisLocation);
 455:             double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
 456:                     rangeAxisLocation);
 457:             double transX1 = domainAxis.valueToJava2D(x1, dataArea, 
 458:                     domainAxisLocation);
 459:             double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 
 460:                     rangeAxisLocation);
 461: 
 462:             if (item > 0) {
 463:                 // get the previous data points...
 464:                 double prevx0 = dataset.getXValue(0, item - 1);
 465:                 double prevy0 = dataset.getYValue(0, item - 1);
 466:                 double prevx1 = dataset.getXValue(1, item - 1);
 467:                 double prevy1 = dataset.getYValue(1, item - 1);
 468: 
 469:                 double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
 470:                         domainAxisLocation);
 471:                 double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea, 
 472:                         rangeAxisLocation);
 473:                 double prevtransX1 = domainAxis.valueToJava2D(prevx1, dataArea, 
 474:                         domainAxisLocation);
 475:                 double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea, 
 476:                         rangeAxisLocation);
 477: 
 478:                 Line2D line0 = null;
 479:                 Line2D line1 = null;
 480:                 if (orientation == PlotOrientation.HORIZONTAL) {
 481:                     line0 = new Line2D.Double(transY0, transX0, prevtransY0, 
 482:                             prevtransX0);
 483:                     line1 = new Line2D.Double(transY1, transX1, prevtransY1, 
 484:                             prevtransX1);
 485:                 }
 486:                 else if (orientation == PlotOrientation.VERTICAL) {
 487:                     line0 = new Line2D.Double(transX0, transY0, prevtransX0, 
 488:                             prevtransY0);
 489:                     line1 = new Line2D.Double(transX1, transY1, prevtransX1, 
 490:                             prevtransY1);
 491:                 }
 492:                 if (line0 != null && line0.intersects(dataArea)) {
 493:                     g2.setPaint(getItemPaint(series, item));
 494:                     g2.setStroke(getItemStroke(series, item));
 495:                     g2.draw(line0);
 496:                 }
 497:                 if (line1 != null && line1.intersects(dataArea)) {
 498:                     g2.setPaint(getItemPaint(1, item));
 499:                     g2.setStroke(getItemStroke(1, item));
 500:                     g2.draw(line1);
 501:                 }
 502:             }
 503: 
 504:             if (getShapesVisible()) {
 505:                 Shape shape0 = getItemShape(series, item);
 506:                 if (orientation == PlotOrientation.HORIZONTAL) {
 507:                     shape0 = ShapeUtilities.createTranslatedShape(shape0, 
 508:                             transY0, transX0);
 509:                 }
 510:                 else {  // vertical
 511:                     shape0 = ShapeUtilities.createTranslatedShape(shape0, 
 512:                             transX0, transY0);
 513:                 }
 514:                 if (shape0.intersects(dataArea)) {
 515:                     g2.setPaint(getItemPaint(series, item));
 516:                     g2.fill(shape0);
 517:                 }
 518:                 entityArea = shape0;
 519: 
 520:                 // add an entity for the item...
 521:                 if (entities != null) {
 522:                     if (entityArea == null) {
 523:                         entityArea = new Rectangle2D.Double(transX0 - 2, 
 524:                                 transY0 - 2, 4, 4);
 525:                     }
 526:                     String tip = null;
 527:                     XYToolTipGenerator generator = getToolTipGenerator(series, 
 528:                             item);
 529:                     if (generator != null) {
 530:                         tip = generator.generateToolTip(dataset, series, item);
 531:                     }
 532:                     String url = null;
 533:                     if (getURLGenerator() != null) {
 534:                         url = getURLGenerator().generateURL(dataset, series, 
 535:                                 item);
 536:                     }
 537:                     XYItemEntity entity = new XYItemEntity(entityArea, dataset, 
 538:                             series, item, tip, url);
 539:                     entities.add(entity);
 540:                 }
 541: 
 542:                 Shape shape1 = getItemShape(series + 1, item);
 543:                 if (orientation == PlotOrientation.HORIZONTAL) {
 544:                     shape1 = ShapeUtilities.createTranslatedShape(shape1, 
 545:                             transY1, transX1);
 546:                 }
 547:                 else {  // vertical
 548:                     shape1 = ShapeUtilities.createTranslatedShape(shape1, 
 549:                             transX1, transY1);
 550:                 }
 551:                 if (shape1.intersects(dataArea)) {
 552:                     g2.setPaint(getItemPaint(series + 1, item));
 553:                     g2.fill(shape1);
 554:                 }
 555:                 entityArea = shape1;
 556: 
 557:                 // add an entity for the item...
 558:                 if (entities != null) {
 559:                     if (entityArea == null) {
 560:                         entityArea = new Rectangle2D.Double(transX1 - 2, 
 561:                                 transY1 - 2, 4, 4);
 562:                     }
 563:                     String tip = null;
 564:                     XYToolTipGenerator generator = getToolTipGenerator(series, 
 565:                             item);
 566:                     if (generator != null) {
 567:                         tip = generator.generateToolTip(dataset, series + 1, 
 568:                                 item);
 569:                     }
 570:                     String url = null;
 571:                     if (getURLGenerator() != null) {
 572:                         url = getURLGenerator().generateURL(dataset, 
 573:                                 series + 1, item);
 574:                     }
 575:                     XYItemEntity entity = new XYItemEntity(entityArea, dataset, 
 576:                             series + 1, item, tip, url);
 577:                     entities.add(entity);
 578:                 }
 579:             }
 580:             updateCrosshairValues(crosshairState, x1, y1, transX1, transY1, 
 581:                     orientation);
 582:         }
 583: 
 584:     }
 585: 
 586:     /**
 587:      * Returns the positive area for a crossover point.
 588:      * 
 589:      * @param x0  x coordinate.
 590:      * @param y0A  y coordinate A.
 591:      * @param y0B  y coordinate B.
 592:      * @param x1  x coordinate.
 593:      * @param y1A  y coordinate A.
 594:      * @param y1B  y coordinate B.
 595:      * @param orientation  the plot orientation.
 596:      * 
 597:      * @return The positive area.
 598:      */
 599:     protected Shape getPositiveArea(float x0, float y0A, float y0B, 
 600:                                     float x1, float y1A, float y1B,
 601:                                     PlotOrientation orientation) {
 602: 
 603:         Shape result = null;
 604: 
 605:         boolean startsNegative = (y0A >= y0B);  
 606:         boolean endsNegative = (y1A >= y1B);
 607:         if (orientation == PlotOrientation.HORIZONTAL) {
 608:             startsNegative = (y0B >= y0A);
 609:             endsNegative = (y1B >= y1A);
 610:         }
 611:         
 612:         if (startsNegative) {  // starts negative
 613:             if (endsNegative) {
 614:                 // all negative - return null
 615:                 result = null;
 616:             }
 617:             else {
 618:                 // changed from negative to positive
 619:                 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
 620:                 GeneralPath area = new GeneralPath();
 621:                 if (orientation == PlotOrientation.HORIZONTAL) {
 622:                     area.moveTo(y1A, x1);
 623:                     area.lineTo(p[1], p[0]);
 624:                     area.lineTo(y1B, x1);
 625:                     area.closePath();
 626:                 }
 627:                 else if (orientation == PlotOrientation.VERTICAL) {
 628:                     area.moveTo(x1, y1A);
 629:                     area.lineTo(p[0], p[1]);
 630:                     area.lineTo(x1, y1B);
 631:                     area.closePath();
 632:                 }
 633:                 result = area;
 634:             }
 635:         }
 636:         else {  // starts positive
 637:             if (endsNegative) {
 638:                 // changed from positive to negative
 639:                 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
 640:                 GeneralPath area = new GeneralPath();
 641:                 if (orientation == PlotOrientation.HORIZONTAL) {
 642:                     area.moveTo(y0A, x0);
 643:                     area.lineTo(p[1], p[0]);
 644:                     area.lineTo(y0B, x0);
 645:                     area.closePath();
 646:                 }
 647:                 else if (orientation == PlotOrientation.VERTICAL) {
 648:                     area.moveTo(x0, y0A);
 649:                     area.lineTo(p[0], p[1]);
 650:                     area.lineTo(x0, y0B);
 651:                     area.closePath();
 652:                 }
 653:                 result = area;
 654: 
 655:             }
 656:             else {
 657:                 GeneralPath area = new GeneralPath();
 658:                 if (orientation == PlotOrientation.HORIZONTAL) {
 659:                     area.moveTo(y0A, x0);
 660:                     area.lineTo(y1A, x1);
 661:                     area.lineTo(y1B, x1);
 662:                     area.lineTo(y0B, x0);
 663:                     area.closePath();
 664:                 }
 665:                 else if (orientation == PlotOrientation.VERTICAL) {
 666:                     area.moveTo(x0, y0A);
 667:                     area.lineTo(x1, y1A);
 668:                     area.lineTo(x1, y1B);
 669:                     area.lineTo(x0, y0B);
 670:                     area.closePath();
 671:                 }
 672:                 result = area;
 673:             }
 674: 
 675:         }
 676: 
 677:         return result;
 678: 
 679:     }
 680: 
 681:     /**
 682:      * Returns the negative area for a cross-over section.
 683:      * 
 684:      * @param x0  x coordinate.
 685:      * @param y0A  y coordinate A.
 686:      * @param y0B  y coordinate B.
 687:      * @param x1  x coordinate.
 688:      * @param y1A  y coordinate A.
 689:      * @param y1B  y coordinate B.
 690:      * @param orientation  the plot orientation.
 691:      * 
 692:      * @return The negative area.
 693:      */
 694:     protected Shape getNegativeArea(float x0, float y0A, float y0B, 
 695:                                     float x1, float y1A, float y1B,
 696:                                     PlotOrientation orientation) {
 697: 
 698:         Shape result = null;
 699: 
 700:         boolean startsNegative = (y0A >= y0B);
 701:         boolean endsNegative = (y1A >= y1B);
 702:         if (orientation == PlotOrientation.HORIZONTAL) {
 703:             startsNegative = (y0B >= y0A);
 704:             endsNegative = (y1B >= y1A);
 705:         }
 706:         if (startsNegative) {  // starts negative
 707:             if (endsNegative) {  // all negative
 708:                 GeneralPath area = new GeneralPath();
 709:                 if (orientation == PlotOrientation.HORIZONTAL) {
 710:                     area.moveTo(y0A, x0);
 711:                     area.lineTo(y1A, x1);
 712:                     area.lineTo(y1B, x1);
 713:                     area.lineTo(y0B, x0);
 714:                     area.closePath();
 715:                 }
 716:                 else if (orientation == PlotOrientation.VERTICAL) {
 717:                     area.moveTo(x0, y0A);
 718:                     area.lineTo(x1, y1A);
 719:                     area.lineTo(x1, y1B);
 720:                     area.lineTo(x0, y0B);
 721:                     area.closePath();
 722:                 }
 723:                 result = area;
 724:             }
 725:             else {  // changed from negative to positive
 726:                 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
 727:                 GeneralPath area = new GeneralPath();
 728:                 if (orientation == PlotOrientation.HORIZONTAL) {
 729:                     area.moveTo(y0A, x0);
 730:                     area.lineTo(p[1], p[0]);
 731:                     area.lineTo(y0B, x0);
 732:                     area.closePath();
 733:                 }
 734:                 else if (orientation == PlotOrientation.VERTICAL) {
 735:                     area.moveTo(x0, y0A);
 736:                     area.lineTo(p[0], p[1]);
 737:                     area.lineTo(x0, y0B);
 738:                     area.closePath();
 739:                 }
 740:                 result = area;
 741:             }
 742:         }
 743:         else {
 744:             if (endsNegative) {
 745:                 // changed from positive to negative
 746:                 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
 747:                 GeneralPath area = new GeneralPath();
 748:                 if (orientation == PlotOrientation.HORIZONTAL) {
 749:                     area.moveTo(y1A, x1);
 750:                     area.lineTo(p[1], p[0]);
 751:                     area.lineTo(y1B, x1);
 752:                     area.closePath();
 753:                 }
 754:                 else if (orientation == PlotOrientation.VERTICAL) {
 755:                     area.moveTo(x1, y1A);
 756:                     area.lineTo(p[0], p[1]);
 757:                     area.lineTo(x1, y1B);
 758:                     area.closePath();
 759:                 }
 760:                 result = area;
 761:             }
 762:             else {
 763:                 // all negative - return null
 764:             }
 765: 
 766:         }
 767: 
 768:         return result;
 769: 
 770:     }
 771: 
 772:     /**
 773:      * Returns the intersection point of two lines.
 774:      * 
 775:      * @param x1  x1
 776:      * @param y1  y1
 777:      * @param x2  x2
 778:      * @param y2  y2
 779:      * @param x3  x3
 780:      * @param y3  y3
 781:      * @param x4  x4
 782:      * @param y4  y4
 783:      * 
 784:      * @return The intersection point.
 785:      */
 786:     private float[] getIntersection(float x1, float y1, float x2, float y2,
 787:                                     float x3, float y3, float x4, float y4) {
 788: 
 789:         float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
 790:         float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
 791:         float u = n / d;
 792: 
 793:         float[] result = new float[2];
 794:         result[0] = x1 + u * (x2 - x1);
 795:         result[1] = y1 + u * (y2 - y1);
 796:         return result;
 797: 
 798:     }
 799:     
 800:     /**
 801:      * Returns a default legend item for the specified series.  Subclasses 
 802:      * should override this method to generate customised items.
 803:      *
 804:      * @param datasetIndex  the dataset index (zero-based).
 805:      * @param series  the series index (zero-based).
 806:      *
 807:      * @return A legend item for the series.
 808:      */
 809:     public LegendItem getLegendItem(int datasetIndex, int series) {
 810:         LegendItem result = null;
 811:         XYPlot p = getPlot();
 812:         if (p != null) {
 813:             XYDataset dataset = p.getDataset(datasetIndex);
 814:             if (dataset != null) {
 815:                 if (getItemVisible(series, 0)) {
 816:                     String label = getLegendItemLabelGenerator().generateLabel(
 817:                             dataset, series);
 818:                     String description = label;
 819:                     String toolTipText = null;
 820:                     if (getLegendItemToolTipGenerator() != null) {
 821:                         toolTipText 
 822:                             = getLegendItemToolTipGenerator().generateLabel(
 823:                                     dataset, series);
 824:                     }
 825:                     String urlText = null;
 826:                     if (getLegendItemURLGenerator() != null) {
 827:                         urlText = getLegendItemURLGenerator().generateLabel(
 828:                                 dataset, series);
 829:                     }
 830:                     Paint paint = getSeriesPaint(series);
 831:                     Stroke stroke = getSeriesStroke(series);
 832:                     // TODO:  the following hard-coded line needs generalising
 833:                     Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
 834:                     result = new LegendItem(label, description, 
 835:                             toolTipText, urlText, line, stroke, paint);
 836:                 }
 837:             }
 838: 
 839:         }
 840: 
 841:         return result;
 842: 
 843:     }
 844: 
 845:     /**
 846:      * Tests this renderer for equality with an arbitrary object.
 847:      * 
 848:      * @param obj  the object (<code>null</code> permitted).
 849:      * 
 850:      * @return A boolean.
 851:      */    
 852:     public boolean equals(Object obj) {
 853:         if (obj == this) {
 854:             return true;   
 855:         }
 856:         if (!(obj instanceof XYDifferenceRenderer)) {
 857:             return false;   
 858:         }
 859:         if (!super.equals(obj)) {
 860:             return false;   
 861:         }
 862:         XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
 863:         if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
 864:             return false;   
 865:         }
 866:         if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
 867:             return false;   
 868:         }
 869:         if (this.shapesVisible != that.shapesVisible) {
 870:             return false;   
 871:         }
 872:         if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
 873:             return false;   
 874:         }
 875:         return true;
 876:     }
 877:     
 878:     /**
 879:      * Returns a clone of the renderer.
 880:      * 
 881:      * @return A clone.
 882:      * 
 883:      * @throws CloneNotSupportedException  if the renderer cannot be cloned.
 884:      */
 885:     public Object clone() throws CloneNotSupportedException {
 886:         return super.clone();
 887:     }
 888: 
 889:     /**
 890:      * Provides serialization support.
 891:      *
 892:      * @param stream  the output stream.
 893:      *
 894:      * @throws IOException  if there is an I/O error.
 895:      */
 896:     private void writeObject(ObjectOutputStream stream) throws IOException {
 897:         stream.defaultWriteObject();
 898:         SerialUtilities.writePaint(this.positivePaint, stream);
 899:         SerialUtilities.writePaint(this.negativePaint, stream);
 900:         SerialUtilities.writeShape(this.legendLine, stream);
 901:     }
 902: 
 903:     /**
 904:      * Provides serialization support.
 905:      *
 906:      * @param stream  the input stream.
 907:      *
 908:      * @throws IOException  if there is an I/O error.
 909:      * @throws ClassNotFoundException  if there is a classpath problem.
 910:      */
 911:     private void readObject(ObjectInputStream stream) 
 912:         throws IOException, ClassNotFoundException {
 913:         stream.defaultReadObject();
 914:         this.positivePaint = SerialUtilities.readPaint(stream);
 915:         this.negativePaint = SerialUtilities.readPaint(stream);
 916:         this.legendLine = SerialUtilities.readShape(stream);
 917:     }
 918: 
 919: }