Source for org.jfree.chart.renderer.category.LineAndShapeRenderer

   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:  * LineAndShapeRenderer.java
  29:  * -------------------------
  30:  * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Mark Watson (www.markwatson.com);
  34:  *                   Jeremy Bowman;
  35:  *                   Richard Atkinson;
  36:  *                   Christian W. Zuckschwerdt;
  37:  *
  38:  * Changes
  39:  * -------
  40:  * 23-Oct-2001 : Version 1 (DG);
  41:  * 15-Nov-2001 : Modified to allow for null data values (DG);
  42:  * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java
  43:  *               --> CategoryItemRenderer.java (DG);
  44:  * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void
  45:  *               to Shape, as part of the tooltips implementation (DG);
  46:  * 11-May-2002 : Support for value label drawing (JB);
  47:  * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
  48:  * 25-Jun-2002 : Removed redundant import (DG);
  49:  * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
  50:  *               for HTML image maps (RA);
  51:  * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  52:  * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL
  53:  *               generators (DG);
  54:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
  55:  *               CategoryToolTipGenerator interface (DG);
  56:  * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
  57:  * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis
  58:  *               for category spacing (DG);
  59:  * 17-Jan-2003 : Moved plot classes to a separate package (DG);
  60:  * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
  61:  *               method (DG);
  62:  * 12-May-2003 : Modified to take into account the plot orientation (DG);
  63:  * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
  64:  * 30-Jul-2003 : Modified entity constructor (CZ);
  65:  * 22-Sep-2003 : Fixed cloning (DG);
  66:  * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste
  67:  *               override easier (DG);
  68:  * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal
  69:  *               charts (DG);
  70:  * 15-Oct-2004 : Updated equals() method (DG);
  71:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  72:  * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
  73:  * 27-Jan-2005 : Changed attribute names, modified constructor and removed
  74:  *               constants (DG);
  75:  * 01-Feb-2005 : Removed unnecessary constants (DG);
  76:  * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG);
  77:  * 13-Apr-2005 : Check flags that control series visibility (DG);
  78:  * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG);
  79:  * 09-Jun-2005 : Use addItemEntity() method (DG);
  80:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  81:  * 25-May-2006 : Added check to drawItem() to detect when both the line and
  82:  *               the shape are not visible (DG);
  83:  * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
  84:  * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
  85:  * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
  86:  * 24-Sep-2007 : Deprecated redundant fields/methods (DG);
  87:  * 27-Sep-2007 : Added option to offset series x-position within category (DG);
  88:  *
  89:  */
  90: 
  91: package org.jfree.chart.renderer.category;
  92: 
  93: import java.awt.Graphics2D;
  94: import java.awt.Paint;
  95: import java.awt.Shape;
  96: import java.awt.Stroke;
  97: import java.awt.geom.Line2D;
  98: import java.awt.geom.Rectangle2D;
  99: import java.io.Serializable;
 100: 
 101: import org.jfree.chart.LegendItem;
 102: import org.jfree.chart.axis.CategoryAxis;
 103: import org.jfree.chart.axis.ValueAxis;
 104: import org.jfree.chart.entity.EntityCollection;
 105: import org.jfree.chart.event.RendererChangeEvent;
 106: import org.jfree.chart.plot.CategoryPlot;
 107: import org.jfree.chart.plot.PlotOrientation;
 108: import org.jfree.data.category.CategoryDataset;
 109: import org.jfree.util.BooleanList;
 110: import org.jfree.util.BooleanUtilities;
 111: import org.jfree.util.ObjectUtilities;
 112: import org.jfree.util.PublicCloneable;
 113: import org.jfree.util.ShapeUtilities;
 114: 
 115: /**
 116:  * A renderer that draws shapes for each data item, and lines between data
 117:  * items (for use with the {@link CategoryPlot} class).
 118:  */
 119: public class LineAndShapeRenderer extends AbstractCategoryItemRenderer
 120:         implements Cloneable, PublicCloneable, Serializable {
 121: 
 122:     /** For serialization. */
 123:     private static final long serialVersionUID = -197749519869226398L;
 124: 
 125:     /**
 126:      * A flag that controls whether or not lines are visible for ALL series.
 127:      *
 128:      * @deprecated As of 1.0.7 (this override flag is unnecessary).
 129:      */
 130:     private Boolean linesVisible;
 131: 
 132:     /**
 133:      * A table of flags that control (per series) whether or not lines are
 134:      * visible.
 135:      */
 136:     private BooleanList seriesLinesVisible;
 137: 
 138:     /**
 139:      * A flag indicating whether or not lines are drawn between non-null
 140:      * points.
 141:      */
 142:     private boolean baseLinesVisible;
 143: 
 144:     /**
 145:      * A flag that controls whether or not shapes are visible for ALL series.
 146:      *
 147:      * @deprecated As of 1.0.7 (this override flag is unnecessary).
 148:      */
 149:     private Boolean shapesVisible;
 150: 
 151:     /**
 152:      * A table of flags that control (per series) whether or not shapes are
 153:      * visible.
 154:      */
 155:     private BooleanList seriesShapesVisible;
 156: 
 157:     /** The default value returned by the getShapeVisible() method. */
 158:     private boolean baseShapesVisible;
 159: 
 160:     /**
 161:      * A flag that controls whether or not shapes are filled for ALL series.
 162:      *
 163:      * @deprecated As of 1.0.7 (this override flag is unnecessary).
 164:      */
 165:     private Boolean shapesFilled;
 166: 
 167:     /**
 168:      * A table of flags that control (per series) whether or not shapes are
 169:      * filled.
 170:      */
 171:     private BooleanList seriesShapesFilled;
 172: 
 173:     /** The default value returned by the getShapeFilled() method. */
 174:     private boolean baseShapesFilled;
 175: 
 176:     /**
 177:      * A flag that controls whether the fill paint is used for filling
 178:      * shapes.
 179:      */
 180:     private boolean useFillPaint;
 181: 
 182:     /** A flag that controls whether outlines are drawn for shapes. */
 183:     private boolean drawOutlines;
 184: 
 185:     /**
 186:      * A flag that controls whether the outline paint is used for drawing shape
 187:      * outlines - if not, the regular series paint is used.
 188:      */
 189:     private boolean useOutlinePaint;
 190: 
 191:     /**
 192:      * A flag that controls whether or not the x-position for each item is
 193:      * offset within the category according to the series.
 194:      *
 195:      * @since 1.0.7
 196:      */
 197:     private boolean useSeriesOffset;
 198: 
 199:     /**
 200:      * The item margin used for series offsetting - this allows the positioning
 201:      * to match the bar positions of the {@link BarRenderer} class.
 202:      *
 203:      * @since 1.0.7
 204:      */
 205:     private double itemMargin;
 206: 
 207:     /**
 208:      * Creates a renderer with both lines and shapes visible by default.
 209:      */
 210:     public LineAndShapeRenderer() {
 211:         this(true, true);
 212:     }
 213: 
 214:     /**
 215:      * Creates a new renderer with lines and/or shapes visible.
 216:      *
 217:      * @param lines  draw lines?
 218:      * @param shapes  draw shapes?
 219:      */
 220:     public LineAndShapeRenderer(boolean lines, boolean shapes) {
 221:         super();
 222:         this.linesVisible = null;
 223:         this.seriesLinesVisible = new BooleanList();
 224:         this.baseLinesVisible = lines;
 225:         this.shapesVisible = null;
 226:         this.seriesShapesVisible = new BooleanList();
 227:         this.baseShapesVisible = shapes;
 228:         this.shapesFilled = null;
 229:         this.seriesShapesFilled = new BooleanList();
 230:         this.baseShapesFilled = true;
 231:         this.useFillPaint = false;
 232:         this.drawOutlines = true;
 233:         this.useOutlinePaint = false;
 234:         this.useSeriesOffset = false;  // preserves old behaviour
 235:         this.itemMargin = 0.0;
 236:     }
 237: 
 238:     // LINES VISIBLE
 239: 
 240:     /**
 241:      * Returns the flag used to control whether or not the line for an item is
 242:      * visible.
 243:      *
 244:      * @param series  the series index (zero-based).
 245:      * @param item  the item index (zero-based).
 246:      *
 247:      * @return A boolean.
 248:      */
 249:     public boolean getItemLineVisible(int series, int item) {
 250:         Boolean flag = this.linesVisible;
 251:         if (flag == null) {
 252:             flag = getSeriesLinesVisible(series);
 253:         }
 254:         if (flag != null) {
 255:             return flag.booleanValue();
 256:         }
 257:         else {
 258:             return this.baseLinesVisible;
 259:         }
 260:     }
 261: 
 262:     /**
 263:      * Returns a flag that controls whether or not lines are drawn for ALL
 264:      * series.  If this flag is <code>null</code>, then the "per series"
 265:      * settings will apply.
 266:      *
 267:      * @return A flag (possibly <code>null</code>).
 268:      *
 269:      * @see #setLinesVisible(Boolean)
 270:      *
 271:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 272:      *     use the per-series and base (default) settings).
 273:      */
 274:     public Boolean getLinesVisible() {
 275:         return this.linesVisible;
 276:     }
 277: 
 278:     /**
 279:      * Sets a flag that controls whether or not lines are drawn between the
 280:      * items in ALL series, and sends a {@link RendererChangeEvent} to all
 281:      * registered listeners.  You need to set this to <code>null</code> if you
 282:      * want the "per series" settings to apply.
 283:      *
 284:      * @param visible  the flag (<code>null</code> permitted).
 285:      *
 286:      * @see #getLinesVisible()
 287:      *
 288:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 289:      *     use the per-series and base (default) settings).
 290:      */
 291:     public void setLinesVisible(Boolean visible) {
 292:         this.linesVisible = visible;
 293:         fireChangeEvent();
 294:     }
 295: 
 296:     /**
 297:      * Sets a flag that controls whether or not lines are drawn between the
 298:      * items in ALL series, and sends a {@link RendererChangeEvent} to all
 299:      * registered listeners.
 300:      *
 301:      * @param visible  the flag.
 302:      *
 303:      * @see #getLinesVisible()
 304:      *
 305:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 306:      *     use the per-series and base (default) settings).
 307:      */
 308:     public void setLinesVisible(boolean visible) {
 309:         setLinesVisible(BooleanUtilities.valueOf(visible));
 310:     }
 311: 
 312:     /**
 313:      * Returns the flag used to control whether or not the lines for a series
 314:      * are visible.
 315:      *
 316:      * @param series  the series index (zero-based).
 317:      *
 318:      * @return The flag (possibly <code>null</code>).
 319:      *
 320:      * @see #setSeriesLinesVisible(int, Boolean)
 321:      */
 322:     public Boolean getSeriesLinesVisible(int series) {
 323:         return this.seriesLinesVisible.getBoolean(series);
 324:     }
 325: 
 326:     /**
 327:      * Sets the 'lines visible' flag for a series and sends a
 328:      * {@link RendererChangeEvent} to all registered listeners.
 329:      *
 330:      * @param series  the series index (zero-based).
 331:      * @param flag  the flag (<code>null</code> permitted).
 332:      *
 333:      * @see #getSeriesLinesVisible(int)
 334:      */
 335:     public void setSeriesLinesVisible(int series, Boolean flag) {
 336:         this.seriesLinesVisible.setBoolean(series, flag);
 337:         fireChangeEvent();
 338:     }
 339: 
 340:     /**
 341:      * Sets the 'lines visible' flag for a series and sends a
 342:      * {@link RendererChangeEvent} to all registered listeners.
 343:      *
 344:      * @param series  the series index (zero-based).
 345:      * @param visible  the flag.
 346:      *
 347:      * @see #getSeriesLinesVisible(int)
 348:      */
 349:     public void setSeriesLinesVisible(int series, boolean visible) {
 350:         setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
 351:     }
 352: 
 353:     /**
 354:      * Returns the base 'lines visible' attribute.
 355:      *
 356:      * @return The base flag.
 357:      *
 358:      * @see #getBaseLinesVisible()
 359:      */
 360:     public boolean getBaseLinesVisible() {
 361:         return this.baseLinesVisible;
 362:     }
 363: 
 364:     /**
 365:      * Sets the base 'lines visible' flag and sends a
 366:      * {@link RendererChangeEvent} to all registered listeners.
 367:      *
 368:      * @param flag  the flag.
 369:      *
 370:      * @see #getBaseLinesVisible()
 371:      */
 372:     public void setBaseLinesVisible(boolean flag) {
 373:         this.baseLinesVisible = flag;
 374:         fireChangeEvent();
 375:     }
 376: 
 377:     // SHAPES VISIBLE
 378: 
 379:     /**
 380:      * Returns the flag used to control whether or not the shape for an item is
 381:      * visible.
 382:      *
 383:      * @param series  the series index (zero-based).
 384:      * @param item  the item index (zero-based).
 385:      *
 386:      * @return A boolean.
 387:      */
 388:     public boolean getItemShapeVisible(int series, int item) {
 389:         Boolean flag = this.shapesVisible;
 390:         if (flag == null) {
 391:             flag = getSeriesShapesVisible(series);
 392:         }
 393:         if (flag != null) {
 394:             return flag.booleanValue();
 395:         }
 396:         else {
 397:             return this.baseShapesVisible;
 398:         }
 399:     }
 400: 
 401:     /**
 402:      * Returns the flag that controls whether the shapes are visible for the
 403:      * items in ALL series.
 404:      *
 405:      * @return The flag (possibly <code>null</code>).
 406:      *
 407:      * @see #setShapesVisible(Boolean)
 408:      *
 409:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 410:      *     use the per-series and base (default) settings).
 411:      */
 412:     public Boolean getShapesVisible() {
 413:         return this.shapesVisible;
 414:     }
 415: 
 416:     /**
 417:      * Sets the 'shapes visible' for ALL series and sends a
 418:      * {@link RendererChangeEvent} to all registered listeners.
 419:      *
 420:      * @param visible  the flag (<code>null</code> permitted).
 421:      *
 422:      * @see #getShapesVisible()
 423:      *
 424:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 425:      *     use the per-series and base (default) settings).
 426:      */
 427:     public void setShapesVisible(Boolean visible) {
 428:         this.shapesVisible = visible;
 429:         fireChangeEvent();
 430:     }
 431: 
 432:     /**
 433:      * Sets the 'shapes visible' for ALL series and sends a
 434:      * {@link RendererChangeEvent} to all registered listeners.
 435:      *
 436:      * @param visible  the flag.
 437:      *
 438:      * @see #getShapesVisible()
 439:      *
 440:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 441:      *     use the per-series and base (default) settings).
 442:      */
 443:     public void setShapesVisible(boolean visible) {
 444:         setShapesVisible(BooleanUtilities.valueOf(visible));
 445:     }
 446: 
 447:     /**
 448:      * Returns the flag used to control whether or not the shapes for a series
 449:      * are visible.
 450:      *
 451:      * @param series  the series index (zero-based).
 452:      *
 453:      * @return A boolean.
 454:      *
 455:      * @see #setSeriesShapesVisible(int, Boolean)
 456:      */
 457:     public Boolean getSeriesShapesVisible(int series) {
 458:         return this.seriesShapesVisible.getBoolean(series);
 459:     }
 460: 
 461:     /**
 462:      * Sets the 'shapes visible' flag for a series and sends a
 463:      * {@link RendererChangeEvent} to all registered listeners.
 464:      *
 465:      * @param series  the series index (zero-based).
 466:      * @param visible  the flag.
 467:      *
 468:      * @see #getSeriesShapesVisible(int)
 469:      */
 470:     public void setSeriesShapesVisible(int series, boolean visible) {
 471:         setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
 472:     }
 473: 
 474:     /**
 475:      * Sets the 'shapes visible' flag for a series and sends a
 476:      * {@link RendererChangeEvent} to all registered listeners.
 477:      *
 478:      * @param series  the series index (zero-based).
 479:      * @param flag  the flag.
 480:      *
 481:      * @see #getSeriesShapesVisible(int)
 482:      */
 483:     public void setSeriesShapesVisible(int series, Boolean flag) {
 484:         this.seriesShapesVisible.setBoolean(series, flag);
 485:         fireChangeEvent();
 486:     }
 487: 
 488:     /**
 489:      * Returns the base 'shape visible' attribute.
 490:      *
 491:      * @return The base flag.
 492:      *
 493:      * @see #setBaseShapesVisible(boolean)
 494:      */
 495:     public boolean getBaseShapesVisible() {
 496:         return this.baseShapesVisible;
 497:     }
 498: 
 499:     /**
 500:      * Sets the base 'shapes visible' flag and sends a
 501:      * {@link RendererChangeEvent} to all registered listeners.
 502:      *
 503:      * @param flag  the flag.
 504:      *
 505:      * @see #getBaseShapesVisible()
 506:      */
 507:     public void setBaseShapesVisible(boolean flag) {
 508:         this.baseShapesVisible = flag;
 509:         fireChangeEvent();
 510:     }
 511: 
 512:     /**
 513:      * Returns <code>true</code> if outlines should be drawn for shapes, and
 514:      * <code>false</code> otherwise.
 515:      *
 516:      * @return A boolean.
 517:      *
 518:      * @see #setDrawOutlines(boolean)
 519:      */
 520:     public boolean getDrawOutlines() {
 521:         return this.drawOutlines;
 522:     }
 523: 
 524:     /**
 525:      * Sets the flag that controls whether outlines are drawn for
 526:      * shapes, and sends a {@link RendererChangeEvent} to all registered
 527:      * listeners.
 528:      * <P>
 529:      * In some cases, shapes look better if they do NOT have an outline, but
 530:      * this flag allows you to set your own preference.
 531:      *
 532:      * @param flag  the flag.
 533:      *
 534:      * @see #getDrawOutlines()
 535:      */
 536:     public void setDrawOutlines(boolean flag) {
 537:         this.drawOutlines = flag;
 538:         fireChangeEvent();
 539:     }
 540: 
 541:     /**
 542:      * Returns the flag that controls whether the outline paint is used for
 543:      * shape outlines.  If not, the regular series paint is used.
 544:      *
 545:      * @return A boolean.
 546:      *
 547:      * @see #setUseOutlinePaint(boolean)
 548:      */
 549:     public boolean getUseOutlinePaint() {
 550:         return this.useOutlinePaint;
 551:     }
 552: 
 553:     /**
 554:      * Sets the flag that controls whether the outline paint is used for shape
 555:      * outlines, and sends a {@link RendererChangeEvent} to all registered
 556:      * listeners.
 557:      *
 558:      * @param use  the flag.
 559:      *
 560:      * @see #getUseOutlinePaint()
 561:      */
 562:     public void setUseOutlinePaint(boolean use) {
 563:         this.useOutlinePaint = use;
 564:         fireChangeEvent();
 565:     }
 566: 
 567:     // SHAPES FILLED
 568: 
 569:     /**
 570:      * Returns the flag used to control whether or not the shape for an item
 571:      * is filled. The default implementation passes control to the
 572:      * <code>getSeriesShapesFilled</code> method. You can override this method
 573:      * if you require different behaviour.
 574:      *
 575:      * @param series  the series index (zero-based).
 576:      * @param item  the item index (zero-based).
 577:      *
 578:      * @return A boolean.
 579:      */
 580:     public boolean getItemShapeFilled(int series, int item) {
 581:         return getSeriesShapesFilled(series);
 582:     }
 583: 
 584:     /**
 585:      * Returns the flag used to control whether or not the shapes for a series
 586:      * are filled.
 587:      *
 588:      * @param series  the series index (zero-based).
 589:      *
 590:      * @return A boolean.
 591:      */
 592:     public boolean getSeriesShapesFilled(int series) {
 593: 
 594:         // return the overall setting, if there is one...
 595:         if (this.shapesFilled != null) {
 596:             return this.shapesFilled.booleanValue();
 597:         }
 598: 
 599:         // otherwise look up the paint table
 600:         Boolean flag = this.seriesShapesFilled.getBoolean(series);
 601:         if (flag != null) {
 602:             return flag.booleanValue();
 603:         }
 604:         else {
 605:             return this.baseShapesFilled;
 606:         }
 607: 
 608:     }
 609: 
 610:     /**
 611:      * Returns the flag that controls whether or not shapes are filled for
 612:      * ALL series.
 613:      *
 614:      * @return A Boolean.
 615:      *
 616:      * @see #setShapesFilled(Boolean)
 617:      *
 618:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 619:      *     use the per-series and base (default) settings).
 620:      */
 621:     public Boolean getShapesFilled() {
 622:         return this.shapesFilled;
 623:     }
 624: 
 625:     /**
 626:      * Sets the 'shapes filled' for ALL series and sends a
 627:      * {@link RendererChangeEvent} to all registered listeners.
 628:      *
 629:      * @param filled  the flag.
 630:      *
 631:      * @see #getShapesFilled()
 632:      *
 633:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 634:      *     use the per-series and base (default) settings).
 635:      */
 636:     public void setShapesFilled(boolean filled) {
 637:         if (filled) {
 638:             setShapesFilled(Boolean.TRUE);
 639:         }
 640:         else {
 641:             setShapesFilled(Boolean.FALSE);
 642:         }
 643:     }
 644: 
 645:     /**
 646:      * Sets the 'shapes filled' for ALL series and sends a
 647:      * {@link RendererChangeEvent} to all registered listeners.
 648:      *
 649:      * @param filled  the flag (<code>null</code> permitted).
 650:      *
 651:      * @see #getShapesFilled()
 652:      *
 653:      * @deprecated As of 1.0.7 (the override facility is unnecessary, just
 654:      *     use the per-series and base (default) settings).
 655:      */
 656:     public void setShapesFilled(Boolean filled) {
 657:         this.shapesFilled = filled;
 658:         fireChangeEvent();
 659:     }
 660: 
 661:     /**
 662:      * Sets the 'shapes filled' flag for a series and sends a
 663:      * {@link RendererChangeEvent} to all registered listeners.
 664:      *
 665:      * @param series  the series index (zero-based).
 666:      * @param filled  the flag.
 667:      *
 668:      * @see #getSeriesShapesFilled(int)
 669:      */
 670:     public void setSeriesShapesFilled(int series, Boolean filled) {
 671:         this.seriesShapesFilled.setBoolean(series, filled);
 672:         fireChangeEvent();
 673:     }
 674: 
 675:     /**
 676:      * Sets the 'shapes filled' flag for a series and sends a
 677:      * {@link RendererChangeEvent} to all registered listeners.
 678:      *
 679:      * @param series  the series index (zero-based).
 680:      * @param filled  the flag.
 681:      *
 682:      * @see #getSeriesShapesFilled(int)
 683:      */
 684:     public void setSeriesShapesFilled(int series, boolean filled) {
 685:         // delegate
 686:         setSeriesShapesFilled(series, BooleanUtilities.valueOf(filled));
 687:     }
 688: 
 689:     /**
 690:      * Returns the base 'shape filled' attribute.
 691:      *
 692:      * @return The base flag.
 693:      *
 694:      * @see #setBaseShapesFilled(boolean)
 695:      */
 696:     public boolean getBaseShapesFilled() {
 697:         return this.baseShapesFilled;
 698:     }
 699: 
 700:     /**
 701:      * Sets the base 'shapes filled' flag and sends a
 702:      * {@link RendererChangeEvent} to all registered listeners.
 703:      *
 704:      * @param flag  the flag.
 705:      *
 706:      * @see #getBaseShapesFilled()
 707:      */
 708:     public void setBaseShapesFilled(boolean flag) {
 709:         this.baseShapesFilled = flag;
 710:         fireChangeEvent();
 711:     }
 712: 
 713:     /**
 714:      * Returns <code>true</code> if the renderer should use the fill paint
 715:      * setting to fill shapes, and <code>false</code> if it should just
 716:      * use the regular paint.
 717:      *
 718:      * @return A boolean.
 719:      *
 720:      * @see #setUseFillPaint(boolean)
 721:      */
 722:     public boolean getUseFillPaint() {
 723:         return this.useFillPaint;
 724:     }
 725: 
 726:     /**
 727:      * Sets the flag that controls whether the fill paint is used to fill
 728:      * shapes, and sends a {@link RendererChangeEvent} to all
 729:      * registered listeners.
 730:      *
 731:      * @param flag  the flag.
 732:      *
 733:      * @see #getUseFillPaint()
 734:      */
 735:     public void setUseFillPaint(boolean flag) {
 736:         this.useFillPaint = flag;
 737:         fireChangeEvent();
 738:     }
 739: 
 740:     /**
 741:      * Returns the flag that controls whether or not the x-position for each
 742:      * data item is offset within the category according to the series.
 743:      *
 744:      * @return A boolean.
 745:      *
 746:      * @see #setUseSeriesOffset(boolean)
 747:      *
 748:      * @since 1.0.7
 749:      */
 750:     public boolean getUseSeriesOffset() {
 751:         return this.useSeriesOffset;
 752:     }
 753: 
 754:     /**
 755:      * Sets the flag that controls whether or not the x-position for each
 756:      * data item is offset within its category according to the series, and
 757:      * sends a {@link RendererChangeEvent} to all registered listeners.
 758:      *
 759:      * @param offset  the offset.
 760:      *
 761:      * @see #getUseSeriesOffset()
 762:      *
 763:      * @since 1.0.7
 764:      */
 765:     public void setUseSeriesOffset(boolean offset) {
 766:         this.useSeriesOffset = offset;
 767:         fireChangeEvent();
 768:     }
 769: 
 770:     /**
 771:      * Returns the item margin, which is the gap between items within a
 772:      * category (expressed as a percentage of the overall category width).
 773:      * This can be used to match the offset alignment with the bars drawn by
 774:      * a {@link BarRenderer}).
 775:      *
 776:      * @return The item margin.
 777:      *
 778:      * @see #setItemMargin(double)
 779:      * @see #getUseSeriesOffset()
 780:      *
 781:      * @since 1.0.7
 782:      */
 783:     public double getItemMargin() {
 784:         return this.itemMargin;
 785:     }
 786: 
 787:     /**
 788:      * Sets the item margin, which is the gap between items within a category
 789:      * (expressed as a percentage of the overall category width), and sends
 790:      * a {@link RendererChangeEvent} to all registered listeners.
 791:      *
 792:      * @param margin  the margin (0.0 <= margin < 1.0).
 793:      *
 794:      * @see #getItemMargin()
 795:      * @see #getUseSeriesOffset()
 796:      *
 797:      * @since 1.0.7
 798:      */
 799:     public void setItemMargin(double margin) {
 800:         if (margin < 0.0 || margin >= 1.0) {
 801:             throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0.");
 802:         }
 803:         this.itemMargin = margin;
 804:         fireChangeEvent();
 805:     }
 806: 
 807:     /**
 808:      * Returns a legend item for a series.
 809:      *
 810:      * @param datasetIndex  the dataset index (zero-based).
 811:      * @param series  the series index (zero-based).
 812:      *
 813:      * @return The legend item.
 814:      */
 815:     public LegendItem getLegendItem(int datasetIndex, int series) {
 816: 
 817:         CategoryPlot cp = getPlot();
 818:         if (cp == null) {
 819:             return null;
 820:         }
 821: 
 822:         if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) {
 823:             CategoryDataset dataset = cp.getDataset(datasetIndex);
 824:             String label = getLegendItemLabelGenerator().generateLabel(
 825:                     dataset, series);
 826:             String description = label;
 827:             String toolTipText = null;
 828:             if (getLegendItemToolTipGenerator() != null) {
 829:                 toolTipText = getLegendItemToolTipGenerator().generateLabel(
 830:                         dataset, series);
 831:             }
 832:             String urlText = null;
 833:             if (getLegendItemURLGenerator() != null) {
 834:                 urlText = getLegendItemURLGenerator().generateLabel(
 835:                         dataset, series);
 836:             }
 837:             Shape shape = lookupSeriesShape(series);
 838:             Paint paint = lookupSeriesPaint(series);
 839:             Paint fillPaint = (this.useFillPaint
 840:                     ? getItemFillPaint(series, 0) : paint);
 841:             boolean shapeOutlineVisible = this.drawOutlines;
 842:             Paint outlinePaint = (this.useOutlinePaint
 843:                     ? getItemOutlinePaint(series, 0) : paint);
 844:             Stroke outlineStroke = lookupSeriesOutlineStroke(series);
 845:             boolean lineVisible = getItemLineVisible(series, 0);
 846:             boolean shapeVisible = getItemShapeVisible(series, 0);
 847:             LegendItem result = new LegendItem(label, description, toolTipText,
 848:                     urlText, shapeVisible, shape, getItemShapeFilled(series, 0),
 849:                     fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke,
 850:                     lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
 851:                     getItemStroke(series, 0), getItemPaint(series, 0));
 852:             result.setDataset(dataset);
 853:             result.setDatasetIndex(datasetIndex);
 854:             result.setSeriesKey(dataset.getRowKey(series));
 855:             result.setSeriesIndex(series);
 856:             return result;
 857:         }
 858:         return null;
 859: 
 860:     }
 861: 
 862:     /**
 863:      * This renderer uses two passes to draw the data.
 864:      *
 865:      * @return The pass count (<code>2</code> for this renderer).
 866:      */
 867:     public int getPassCount() {
 868:         return 2;
 869:     }
 870: 
 871:     /**
 872:      * Draw a single data item.
 873:      *
 874:      * @param g2  the graphics device.
 875:      * @param state  the renderer state.
 876:      * @param dataArea  the area in which the data is drawn.
 877:      * @param plot  the plot.
 878:      * @param domainAxis  the domain axis.
 879:      * @param rangeAxis  the range axis.
 880:      * @param dataset  the dataset.
 881:      * @param row  the row index (zero-based).
 882:      * @param column  the column index (zero-based).
 883:      * @param pass  the pass index.
 884:      */
 885:     public void drawItem(Graphics2D g2, CategoryItemRendererState state,
 886:             Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
 887:             ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
 888:             int pass) {
 889: 
 890:         // do nothing if item is not visible
 891:         if (!getItemVisible(row, column)) {
 892:             return;
 893:         }
 894: 
 895:         // do nothing if both the line and shape are not visible
 896:         if (!getItemLineVisible(row, column)
 897:                 && !getItemShapeVisible(row, column)) {
 898:             return;
 899:         }
 900: 
 901:         // nothing is drawn for null...
 902:         Number v = dataset.getValue(row, column);
 903:         if (v == null) {
 904:             return;
 905:         }
 906: 
 907:         PlotOrientation orientation = plot.getOrientation();
 908: 
 909:         // current data point...
 910:         double x1;
 911:         if (this.useSeriesOffset) {
 912:             x1 = domainAxis.getCategorySeriesMiddle(dataset.getColumnKey(
 913:                     column), dataset.getRowKey(row), dataset, this.itemMargin,
 914:                     dataArea, plot.getDomainAxisEdge());
 915:         }
 916:         else {
 917:             x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
 918:                     dataArea, plot.getDomainAxisEdge());
 919:         }
 920:         double value = v.doubleValue();
 921:         double y1 = rangeAxis.valueToJava2D(value, dataArea,
 922:                 plot.getRangeAxisEdge());
 923: 
 924:         if (pass == 0 && getItemLineVisible(row, column)) {
 925:             if (column != 0) {
 926:                 Number previousValue = dataset.getValue(row, column - 1);
 927:                 if (previousValue != null) {
 928:                     // previous data point...
 929:                     double previous = previousValue.doubleValue();
 930:                     double x0;
 931:                     if (this.useSeriesOffset) {
 932:                         x0 = domainAxis.getCategorySeriesMiddle(
 933:                                 dataset.getColumnKey(column - 1),
 934:                                 dataset.getRowKey(row), dataset,
 935:                                 this.itemMargin, dataArea,
 936:                                 plot.getDomainAxisEdge());
 937:                     }
 938:                     else {
 939:                         x0 = domainAxis.getCategoryMiddle(column - 1,
 940:                                 getColumnCount(), dataArea,
 941:                                 plot.getDomainAxisEdge());
 942:                     }
 943:                     double y0 = rangeAxis.valueToJava2D(previous, dataArea,
 944:                             plot.getRangeAxisEdge());
 945: 
 946:                     Line2D line = null;
 947:                     if (orientation == PlotOrientation.HORIZONTAL) {
 948:                         line = new Line2D.Double(y0, x0, y1, x1);
 949:                     }
 950:                     else if (orientation == PlotOrientation.VERTICAL) {
 951:                         line = new Line2D.Double(x0, y0, x1, y1);
 952:                     }
 953:                     g2.setPaint(getItemPaint(row, column));
 954:                     g2.setStroke(getItemStroke(row, column));
 955:                     g2.draw(line);
 956:                 }
 957:             }
 958:         }
 959: 
 960:         if (pass == 1) {
 961:             Shape shape = getItemShape(row, column);
 962:             if (orientation == PlotOrientation.HORIZONTAL) {
 963:                 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
 964:             }
 965:             else if (orientation == PlotOrientation.VERTICAL) {
 966:                 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
 967:             }
 968: 
 969:             if (getItemShapeVisible(row, column)) {
 970:                 if (getItemShapeFilled(row, column)) {
 971:                     if (this.useFillPaint) {
 972:                         g2.setPaint(getItemFillPaint(row, column));
 973:                     }
 974:                     else {
 975:                         g2.setPaint(getItemPaint(row, column));
 976:                     }
 977:                     g2.fill(shape);
 978:                 }
 979:                 if (this.drawOutlines) {
 980:                     if (this.useOutlinePaint) {
 981:                         g2.setPaint(getItemOutlinePaint(row, column));
 982:                     }
 983:                     else {
 984:                         g2.setPaint(getItemPaint(row, column));
 985:                     }
 986:                     g2.setStroke(getItemOutlineStroke(row, column));
 987:                     g2.draw(shape);
 988:                 }
 989:             }
 990: 
 991:             // draw the item label if there is one...
 992:             if (isItemLabelVisible(row, column)) {
 993:                 if (orientation == PlotOrientation.HORIZONTAL) {
 994:                     drawItemLabel(g2, orientation, dataset, row, column, y1,
 995:                             x1, (value < 0.0));
 996:                 }
 997:                 else if (orientation == PlotOrientation.VERTICAL) {
 998:                     drawItemLabel(g2, orientation, dataset, row, column, x1,
 999:                             y1, (value < 0.0));
1000:                 }
1001:             }
1002: 
1003:             // add an item entity, if this information is being collected
1004:             EntityCollection entities = state.getEntityCollection();
1005:             if (entities != null) {
1006:                 addItemEntity(entities, dataset, row, column, shape);
1007:             }
1008:         }
1009: 
1010:     }
1011: 
1012:     /**
1013:      * Tests this renderer for equality with an arbitrary object.
1014:      *
1015:      * @param obj  the object (<code>null</code> permitted).
1016:      *
1017:      * @return A boolean.
1018:      */
1019:     public boolean equals(Object obj) {
1020: 
1021:         if (obj == this) {
1022:             return true;
1023:         }
1024:         if (!(obj instanceof LineAndShapeRenderer)) {
1025:             return false;
1026:         }
1027: 
1028:         LineAndShapeRenderer that = (LineAndShapeRenderer) obj;
1029:         if (this.baseLinesVisible != that.baseLinesVisible) {
1030:             return false;
1031:         }
1032:         if (!ObjectUtilities.equal(this.seriesLinesVisible,
1033:                 that.seriesLinesVisible)) {
1034:             return false;
1035:         }
1036:         if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
1037:             return false;
1038:         }
1039:         if (this.baseShapesVisible != that.baseShapesVisible) {
1040:             return false;
1041:         }
1042:         if (!ObjectUtilities.equal(this.seriesShapesVisible,
1043:                 that.seriesShapesVisible)) {
1044:             return false;
1045:         }
1046:         if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
1047:             return false;
1048:         }
1049:         if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1050:             return false;
1051:         }
1052:         if (!ObjectUtilities.equal(this.seriesShapesFilled,
1053:                 that.seriesShapesFilled)) {
1054:             return false;
1055:         }
1056:         if (this.baseShapesFilled != that.baseShapesFilled) {
1057:             return false;
1058:         }
1059:         if (this.useOutlinePaint != that.useOutlinePaint) {
1060:             return false;
1061:         }
1062:         if (this.useSeriesOffset != that.useSeriesOffset) {
1063:             return false;
1064:         }
1065:         if (this.itemMargin != that.itemMargin) {
1066:             return false;
1067:         }
1068:         return super.equals(obj);
1069:     }
1070: 
1071:     /**
1072:      * Returns an independent copy of the renderer.
1073:      *
1074:      * @return A clone.
1075:      *
1076:      * @throws CloneNotSupportedException  should not happen.
1077:      */
1078:     public Object clone() throws CloneNotSupportedException {
1079:         LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone();
1080:         clone.seriesLinesVisible
1081:             = (BooleanList) this.seriesLinesVisible.clone();
1082:         clone.seriesShapesVisible
1083:             = (BooleanList) this.seriesShapesVisible.clone();
1084:         clone.seriesShapesFilled
1085:             = (BooleanList) this.seriesShapesFilled.clone();
1086:         return clone;
1087:     }
1088: 
1089: }