001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * --------------------------- 028 * AbstractXYItemRenderer.java 029 * --------------------------- 030 * (C) Copyright 2002-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Richard Atkinson; 034 * Focus Computer Services Limited; 035 * Tim Bardzil; 036 * Sergei Ivanov; 037 * 038 * Changes: 039 * -------- 040 * 15-Mar-2002 : Version 1 (DG); 041 * 09-Apr-2002 : Added a getToolTipGenerator() method reflecting the change in 042 * the XYItemRenderer interface (DG); 043 * 05-Aug-2002 : Added a urlGenerator member variable to support HTML image 044 * maps (RA); 045 * 20-Aug-2002 : Added property change events for the tooltip and URL 046 * generators (DG); 047 * 22-Aug-2002 : Moved property change support into AbstractRenderer class (DG); 048 * 23-Sep-2002 : Fixed errors reported by Checkstyle tool (DG); 049 * 18-Nov-2002 : Added methods for drawing grid lines (DG); 050 * 17-Jan-2003 : Moved plot classes into a separate package (DG); 051 * 25-Mar-2003 : Implemented Serializable (DG); 052 * 01-May-2003 : Modified initialise() return type and drawItem() method 053 * signature (DG); 054 * 15-May-2003 : Modified to take into account the plot orientation (DG); 055 * 21-May-2003 : Added labels to markers (DG); 056 * 05-Jun-2003 : Added domain and range grid bands (sponsored by Focus Computer 057 * Services Ltd) (DG); 058 * 27-Jul-2003 : Added getRangeType() to support stacked XY area charts (RA); 059 * 31-Jul-2003 : Deprecated all but the default constructor (DG); 060 * 13-Aug-2003 : Implemented Cloneable (DG); 061 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 062 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 063 * 05-Nov-2003 : Fixed marker rendering bug (833623) (DG); 064 * 11-Feb-2004 : Updated labelling for markers (DG); 065 * 25-Feb-2004 : Added updateCrosshairValues() method. Moved deprecated code 066 * to bottom of source file (DG); 067 * 16-Apr-2004 : Added support for IntervalMarker in drawRangeMarker() method 068 * - thanks to Tim Bardzil (DG); 069 * 05-May-2004 : Fixed bug (948310) where interval markers extend beyond axis 070 * range (DG); 071 * 03-Jun-2004 : Fixed more bugs in drawing interval markers (DG); 072 * 26-Aug-2004 : Added the addEntity() method (DG); 073 * 29-Sep-2004 : Added annotation support (with layers) (DG); 074 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities --> 075 * TextUtilities (DG); 076 * 06-Oct-2004 : Added findDomainBounds() method and renamed 077 * getRangeExtent() --> findRangeBounds() (DG); 078 * 07-Jan-2005 : Removed deprecated code (DG); 079 * 27-Jan-2005 : Modified getLegendItem() to omit hidden series (DG); 080 * 24-Feb-2005 : Added getLegendItems() method (DG); 081 * 08-Mar-2005 : Fixed positioning of marker labels (DG); 082 * 20-Apr-2005 : Renamed XYLabelGenerator --> XYItemLabelGenerator and 083 * added generators for legend labels, tooltips and URLs (DG); 084 * 01-Jun-2005 : Handle one dimension of the marker label adjustment 085 * automatically (DG); 086 * ------------- JFREECHART 1.0.x --------------------------------------------- 087 * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG); 088 * 24-Oct-2006 : Respect alpha setting in markers (see patch 1567843 by Sergei 089 * Ivanov) (DG); 090 * 24-Oct-2006 : Added code to draw outlines for interval markers (DG); 091 * 24-Nov-2006 : Fixed cloning for legend item generators (DG); 092 * 06-Feb-2007 : Added new updateCrosshairValues() method that takes into 093 * account multiple axis plots (see bug 1086307) (DG); 094 * 20-Feb-2007 : Fixed equals() method implementation (DG); 095 * 01-Mar-2007 : Fixed interval marker drawing (patch 1670686 thanks to 096 * Sergei Ivanov) (DG); 097 * 22-Mar-2007 : Modified the tool tip generator look up (DG); 098 * 23-Mar-2007 : Added drawDomainLine() method (DG); 099 * 20-Apr-2007 : Updated getLegendItem() for renderer change, and deprecated 100 * itemLabelGenerator and toolTipGenerator override fields (DG); 101 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 102 * 12-Nov-2007 : Fixed domain and range band drawing methods (DG); 103 * 07-Apr-2008 : Minor API doc update (DG); 104 * 14-May-2008 : Updated addEntity() method to take plot orientation into 105 * account when the incoming area is null (DG); 106 * 02-Jun-2008 : Added isPointInRect() method (DG); 107 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG); 108 * 109 */ 110 111 package org.jfree.chart.renderer.xy; 112 113 import java.awt.AlphaComposite; 114 import java.awt.Composite; 115 import java.awt.Font; 116 import java.awt.GradientPaint; 117 import java.awt.Graphics2D; 118 import java.awt.Paint; 119 import java.awt.Shape; 120 import java.awt.Stroke; 121 import java.awt.geom.Ellipse2D; 122 import java.awt.geom.Line2D; 123 import java.awt.geom.Point2D; 124 import java.awt.geom.Rectangle2D; 125 import java.io.Serializable; 126 import java.util.Iterator; 127 import java.util.List; 128 129 import org.jfree.chart.LegendItem; 130 import org.jfree.chart.LegendItemCollection; 131 import org.jfree.chart.annotations.XYAnnotation; 132 import org.jfree.chart.axis.ValueAxis; 133 import org.jfree.chart.entity.EntityCollection; 134 import org.jfree.chart.entity.XYItemEntity; 135 import org.jfree.chart.event.RendererChangeEvent; 136 import org.jfree.chart.labels.ItemLabelPosition; 137 import org.jfree.chart.labels.StandardXYSeriesLabelGenerator; 138 import org.jfree.chart.labels.XYItemLabelGenerator; 139 import org.jfree.chart.labels.XYSeriesLabelGenerator; 140 import org.jfree.chart.labels.XYToolTipGenerator; 141 import org.jfree.chart.plot.CrosshairState; 142 import org.jfree.chart.plot.DrawingSupplier; 143 import org.jfree.chart.plot.IntervalMarker; 144 import org.jfree.chart.plot.Marker; 145 import org.jfree.chart.plot.Plot; 146 import org.jfree.chart.plot.PlotOrientation; 147 import org.jfree.chart.plot.PlotRenderingInfo; 148 import org.jfree.chart.plot.ValueMarker; 149 import org.jfree.chart.plot.XYPlot; 150 import org.jfree.chart.renderer.AbstractRenderer; 151 import org.jfree.chart.urls.XYURLGenerator; 152 import org.jfree.data.Range; 153 import org.jfree.data.general.DatasetUtilities; 154 import org.jfree.data.xy.XYDataset; 155 import org.jfree.text.TextUtilities; 156 import org.jfree.ui.GradientPaintTransformer; 157 import org.jfree.ui.Layer; 158 import org.jfree.ui.LengthAdjustmentType; 159 import org.jfree.ui.RectangleAnchor; 160 import org.jfree.ui.RectangleInsets; 161 import org.jfree.util.ObjectList; 162 import org.jfree.util.ObjectUtilities; 163 import org.jfree.util.PublicCloneable; 164 165 /** 166 * A base class that can be used to create new {@link XYItemRenderer} 167 * implementations. 168 */ 169 public abstract class AbstractXYItemRenderer extends AbstractRenderer 170 implements XYItemRenderer, Cloneable, Serializable { 171 172 /** For serialization. */ 173 private static final long serialVersionUID = 8019124836026607990L; 174 175 /** The plot. */ 176 private XYPlot plot; 177 178 /** 179 * The item label generator for ALL series. 180 * 181 * @deprecated This field is redundant, use itemLabelGeneratorList and 182 * baseItemLabelGenerator instead. Deprecated as of version 1.0.6. 183 */ 184 private XYItemLabelGenerator itemLabelGenerator; 185 186 /** A list of item label generators (one per series). */ 187 private ObjectList itemLabelGeneratorList; 188 189 /** The base item label generator. */ 190 private XYItemLabelGenerator baseItemLabelGenerator; 191 192 /** 193 * The tool tip generator for ALL series. 194 * 195 * @deprecated This field is redundant, use tooltipGeneratorList and 196 * baseToolTipGenerator instead. Deprecated as of version 1.0.6. 197 */ 198 private XYToolTipGenerator toolTipGenerator; 199 200 /** A list of tool tip generators (one per series). */ 201 private ObjectList toolTipGeneratorList; 202 203 /** The base tool tip generator. */ 204 private XYToolTipGenerator baseToolTipGenerator; 205 206 /** The URL text generator. */ 207 private XYURLGenerator urlGenerator; 208 209 /** 210 * Annotations to be drawn in the background layer ('underneath' the data 211 * items). 212 */ 213 private List backgroundAnnotations; 214 215 /** 216 * Annotations to be drawn in the foreground layer ('on top' of the data 217 * items). 218 */ 219 private List foregroundAnnotations; 220 221 /** The default radius for the entity 'hotspot' */ 222 private int defaultEntityRadius; 223 224 /** The legend item label generator. */ 225 private XYSeriesLabelGenerator legendItemLabelGenerator; 226 227 /** The legend item tool tip generator. */ 228 private XYSeriesLabelGenerator legendItemToolTipGenerator; 229 230 /** The legend item URL generator. */ 231 private XYSeriesLabelGenerator legendItemURLGenerator; 232 233 /** 234 * Creates a renderer where the tooltip generator and the URL generator are 235 * both <code>null</code>. 236 */ 237 protected AbstractXYItemRenderer() { 238 super(); 239 this.itemLabelGenerator = null; 240 this.itemLabelGeneratorList = new ObjectList(); 241 this.toolTipGenerator = null; 242 this.toolTipGeneratorList = new ObjectList(); 243 this.urlGenerator = null; 244 this.backgroundAnnotations = new java.util.ArrayList(); 245 this.foregroundAnnotations = new java.util.ArrayList(); 246 this.defaultEntityRadius = 3; 247 this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator( 248 "{0}"); 249 } 250 251 /** 252 * Returns the number of passes through the data that the renderer requires 253 * in order to draw the chart. Most charts will require a single pass, but 254 * some require two passes. 255 * 256 * @return The pass count. 257 */ 258 public int getPassCount() { 259 return 1; 260 } 261 262 /** 263 * Returns the plot that the renderer is assigned to. 264 * 265 * @return The plot (possibly <code>null</code>). 266 */ 267 public XYPlot getPlot() { 268 return this.plot; 269 } 270 271 /** 272 * Sets the plot that the renderer is assigned to. 273 * 274 * @param plot the plot (<code>null</code> permitted). 275 */ 276 public void setPlot(XYPlot plot) { 277 this.plot = plot; 278 } 279 280 /** 281 * Initialises the renderer and returns a state object that should be 282 * passed to all subsequent calls to the drawItem() method. 283 * <P> 284 * This method will be called before the first item is rendered, giving the 285 * renderer an opportunity to initialise any state information it wants to 286 * maintain. The renderer can do nothing if it chooses. 287 * 288 * @param g2 the graphics device. 289 * @param dataArea the area inside the axes. 290 * @param plot the plot. 291 * @param data the data. 292 * @param info an optional info collection object to return data back to 293 * the caller. 294 * 295 * @return The renderer state (never <code>null</code>). 296 */ 297 public XYItemRendererState initialise(Graphics2D g2, 298 Rectangle2D dataArea, 299 XYPlot plot, 300 XYDataset data, 301 PlotRenderingInfo info) { 302 303 XYItemRendererState state = new XYItemRendererState(info); 304 return state; 305 306 } 307 308 // ITEM LABEL GENERATOR 309 310 /** 311 * Returns the label generator for a data item. This implementation simply 312 * passes control to the {@link #getSeriesItemLabelGenerator(int)} method. 313 * If, for some reason, you want a different generator for individual 314 * items, you can override this method. 315 * 316 * @param series the series index (zero based). 317 * @param item the item index (zero based). 318 * 319 * @return The generator (possibly <code>null</code>). 320 */ 321 public XYItemLabelGenerator getItemLabelGenerator(int series, int item) { 322 // return the generator for ALL series, if there is one... 323 if (this.itemLabelGenerator != null) { 324 return this.itemLabelGenerator; 325 } 326 327 // otherwise look up the generator table 328 XYItemLabelGenerator generator 329 = (XYItemLabelGenerator) this.itemLabelGeneratorList.get(series); 330 if (generator == null) { 331 generator = this.baseItemLabelGenerator; 332 } 333 return generator; 334 } 335 336 /** 337 * Returns the item label generator for a series. 338 * 339 * @param series the series index (zero based). 340 * 341 * @return The generator (possibly <code>null</code>). 342 */ 343 public XYItemLabelGenerator getSeriesItemLabelGenerator(int series) { 344 return (XYItemLabelGenerator) this.itemLabelGeneratorList.get(series); 345 } 346 347 /** 348 * Returns the item label generator override. 349 * 350 * @return The generator (possibly <code>null</code>). 351 * 352 * @since 1.0.5 353 * 354 * @see #setItemLabelGenerator(XYItemLabelGenerator) 355 * 356 * @deprecated As of version 1.0.6, this override setting should not be 357 * used. You can use the base setting instead 358 * ({@link #getBaseItemLabelGenerator()}). 359 */ 360 public XYItemLabelGenerator getItemLabelGenerator() { 361 return this.itemLabelGenerator; 362 } 363 364 /** 365 * Sets the item label generator for ALL series and sends a 366 * {@link RendererChangeEvent} to all registered listeners. 367 * 368 * @param generator the generator (<code>null</code> permitted). 369 * 370 * @see #getItemLabelGenerator() 371 * 372 * @deprecated As of version 1.0.6, this override setting should not be 373 * used. You can use the base setting instead 374 * ({@link #setBaseItemLabelGenerator(XYItemLabelGenerator)}). 375 */ 376 public void setItemLabelGenerator(XYItemLabelGenerator generator) { 377 this.itemLabelGenerator = generator; 378 fireChangeEvent(); 379 } 380 381 /** 382 * Sets the item label generator for a series and sends a 383 * {@link RendererChangeEvent} to all registered listeners. 384 * 385 * @param series the series index (zero based). 386 * @param generator the generator (<code>null</code> permitted). 387 */ 388 public void setSeriesItemLabelGenerator(int series, 389 XYItemLabelGenerator generator) { 390 this.itemLabelGeneratorList.set(series, generator); 391 fireChangeEvent(); 392 } 393 394 /** 395 * Returns the base item label generator. 396 * 397 * @return The generator (possibly <code>null</code>). 398 */ 399 public XYItemLabelGenerator getBaseItemLabelGenerator() { 400 return this.baseItemLabelGenerator; 401 } 402 403 /** 404 * Sets the base item label generator and sends a 405 * {@link RendererChangeEvent} to all registered listeners. 406 * 407 * @param generator the generator (<code>null</code> permitted). 408 */ 409 public void setBaseItemLabelGenerator(XYItemLabelGenerator generator) { 410 this.baseItemLabelGenerator = generator; 411 fireChangeEvent(); 412 } 413 414 // TOOL TIP GENERATOR 415 416 /** 417 * Returns the tool tip generator for a data item. If, for some reason, 418 * you want a different generator for individual items, you can override 419 * this method. 420 * 421 * @param series the series index (zero based). 422 * @param item the item index (zero based). 423 * 424 * @return The generator (possibly <code>null</code>). 425 */ 426 public XYToolTipGenerator getToolTipGenerator(int series, int item) { 427 // return the generator for ALL series, if there is one... 428 if (this.toolTipGenerator != null) { 429 return this.toolTipGenerator; 430 } 431 432 // otherwise look up the generator table 433 XYToolTipGenerator generator 434 = (XYToolTipGenerator) this.toolTipGeneratorList.get(series); 435 if (generator == null) { 436 generator = this.baseToolTipGenerator; 437 } 438 return generator; 439 } 440 441 /** 442 * Returns the override tool tip generator. 443 * 444 * @return The tool tip generator (possible <code>null</code>). 445 * 446 * @since 1.0.5 447 * 448 * @see #setToolTipGenerator(XYToolTipGenerator) 449 * 450 * @deprecated As of version 1.0.6, this override setting should not be 451 * used. You can use the base setting instead 452 * ({@link #getBaseToolTipGenerator()}). 453 */ 454 public XYToolTipGenerator getToolTipGenerator() { 455 return this.toolTipGenerator; 456 } 457 458 /** 459 * Sets the tool tip generator for ALL series and sends a 460 * {@link RendererChangeEvent} to all registered listeners. 461 * 462 * @param generator the generator (<code>null</code> permitted). 463 * 464 * @see #getToolTipGenerator() 465 * 466 * @deprecated As of version 1.0.6, this override setting should not be 467 * used. You can use the base setting instead 468 * ({@link #setBaseToolTipGenerator(XYToolTipGenerator)}). 469 */ 470 public void setToolTipGenerator(XYToolTipGenerator generator) { 471 this.toolTipGenerator = generator; 472 fireChangeEvent(); 473 } 474 475 /** 476 * Returns the tool tip generator for a series. 477 * 478 * @param series the series index (zero based). 479 * 480 * @return The generator (possibly <code>null</code>). 481 */ 482 public XYToolTipGenerator getSeriesToolTipGenerator(int series) { 483 return (XYToolTipGenerator) this.toolTipGeneratorList.get(series); 484 } 485 486 /** 487 * Sets the tool tip generator for a series and sends a 488 * {@link RendererChangeEvent} to all registered listeners. 489 * 490 * @param series the series index (zero based). 491 * @param generator the generator (<code>null</code> permitted). 492 */ 493 public void setSeriesToolTipGenerator(int series, 494 XYToolTipGenerator generator) { 495 this.toolTipGeneratorList.set(series, generator); 496 fireChangeEvent(); 497 } 498 499 /** 500 * Returns the base tool tip generator. 501 * 502 * @return The generator (possibly <code>null</code>). 503 * 504 * @see #setBaseToolTipGenerator(XYToolTipGenerator) 505 */ 506 public XYToolTipGenerator getBaseToolTipGenerator() { 507 return this.baseToolTipGenerator; 508 } 509 510 /** 511 * Sets the base tool tip generator and sends a {@link RendererChangeEvent} 512 * to all registered listeners. 513 * 514 * @param generator the generator (<code>null</code> permitted). 515 * 516 * @see #getBaseToolTipGenerator() 517 */ 518 public void setBaseToolTipGenerator(XYToolTipGenerator generator) { 519 this.baseToolTipGenerator = generator; 520 fireChangeEvent(); 521 } 522 523 // URL GENERATOR 524 525 /** 526 * Returns the URL generator for HTML image maps. 527 * 528 * @return The URL generator (possibly <code>null</code>). 529 */ 530 public XYURLGenerator getURLGenerator() { 531 return this.urlGenerator; 532 } 533 534 /** 535 * Sets the URL generator for HTML image maps and sends a 536 * {@link RendererChangeEvent} to all registered listeners. 537 * 538 * @param urlGenerator the URL generator (<code>null</code> permitted). 539 */ 540 public void setURLGenerator(XYURLGenerator urlGenerator) { 541 this.urlGenerator = urlGenerator; 542 fireChangeEvent(); 543 } 544 545 /** 546 * Adds an annotation and sends a {@link RendererChangeEvent} to all 547 * registered listeners. The annotation is added to the foreground 548 * layer. 549 * 550 * @param annotation the annotation (<code>null</code> not permitted). 551 */ 552 public void addAnnotation(XYAnnotation annotation) { 553 // defer argument checking 554 addAnnotation(annotation, Layer.FOREGROUND); 555 } 556 557 /** 558 * Adds an annotation to the specified layer and sends a 559 * {@link RendererChangeEvent} to all registered listeners. 560 * 561 * @param annotation the annotation (<code>null</code> not permitted). 562 * @param layer the layer (<code>null</code> not permitted). 563 */ 564 public void addAnnotation(XYAnnotation annotation, Layer layer) { 565 if (annotation == null) { 566 throw new IllegalArgumentException("Null 'annotation' argument."); 567 } 568 if (layer.equals(Layer.FOREGROUND)) { 569 this.foregroundAnnotations.add(annotation); 570 fireChangeEvent(); 571 } 572 else if (layer.equals(Layer.BACKGROUND)) { 573 this.backgroundAnnotations.add(annotation); 574 fireChangeEvent(); 575 } 576 else { 577 // should never get here 578 throw new RuntimeException("Unknown layer."); 579 } 580 } 581 /** 582 * Removes the specified annotation and sends a {@link RendererChangeEvent} 583 * to all registered listeners. 584 * 585 * @param annotation the annotation to remove (<code>null</code> not 586 * permitted). 587 * 588 * @return A boolean to indicate whether or not the annotation was 589 * successfully removed. 590 */ 591 public boolean removeAnnotation(XYAnnotation annotation) { 592 boolean removed = this.foregroundAnnotations.remove(annotation); 593 removed = removed & this.backgroundAnnotations.remove(annotation); 594 fireChangeEvent(); 595 return removed; 596 } 597 598 /** 599 * Removes all annotations and sends a {@link RendererChangeEvent} 600 * to all registered listeners. 601 */ 602 public void removeAnnotations() { 603 this.foregroundAnnotations.clear(); 604 this.backgroundAnnotations.clear(); 605 fireChangeEvent(); 606 } 607 608 /** 609 * Returns the radius of the circle used for the default entity area 610 * when no area is specified. 611 * 612 * @return A radius. 613 * 614 * @see #setDefaultEntityRadius(int) 615 */ 616 public int getDefaultEntityRadius() { 617 return this.defaultEntityRadius; 618 } 619 620 /** 621 * Sets the radius of the circle used for the default entity area 622 * when no area is specified. 623 * 624 * @param radius the radius. 625 * 626 * @see #getDefaultEntityRadius() 627 */ 628 public void setDefaultEntityRadius(int radius) { 629 this.defaultEntityRadius = radius; 630 } 631 632 /** 633 * Returns the legend item label generator. 634 * 635 * @return The label generator (never <code>null</code>). 636 * 637 * @see #setLegendItemLabelGenerator(XYSeriesLabelGenerator) 638 */ 639 public XYSeriesLabelGenerator getLegendItemLabelGenerator() { 640 return this.legendItemLabelGenerator; 641 } 642 643 /** 644 * Sets the legend item label generator and sends a 645 * {@link RendererChangeEvent} to all registered listeners. 646 * 647 * @param generator the generator (<code>null</code> not permitted). 648 * 649 * @see #getLegendItemLabelGenerator() 650 */ 651 public void setLegendItemLabelGenerator(XYSeriesLabelGenerator generator) { 652 if (generator == null) { 653 throw new IllegalArgumentException("Null 'generator' argument."); 654 } 655 this.legendItemLabelGenerator = generator; 656 fireChangeEvent(); 657 } 658 659 /** 660 * Returns the legend item tool tip generator. 661 * 662 * @return The tool tip generator (possibly <code>null</code>). 663 * 664 * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) 665 */ 666 public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { 667 return this.legendItemToolTipGenerator; 668 } 669 670 /** 671 * Sets the legend item tool tip generator and sends a 672 * {@link RendererChangeEvent} to all registered listeners. 673 * 674 * @param generator the generator (<code>null</code> permitted). 675 * 676 * @see #getLegendItemToolTipGenerator() 677 */ 678 public void setLegendItemToolTipGenerator( 679 XYSeriesLabelGenerator generator) { 680 this.legendItemToolTipGenerator = generator; 681 fireChangeEvent(); 682 } 683 684 /** 685 * Returns the legend item URL generator. 686 * 687 * @return The URL generator (possibly <code>null</code>). 688 * 689 * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) 690 */ 691 public XYSeriesLabelGenerator getLegendItemURLGenerator() { 692 return this.legendItemURLGenerator; 693 } 694 695 /** 696 * Sets the legend item URL generator and sends a 697 * {@link RendererChangeEvent} to all registered listeners. 698 * 699 * @param generator the generator (<code>null</code> permitted). 700 * 701 * @see #getLegendItemURLGenerator() 702 */ 703 public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { 704 this.legendItemURLGenerator = generator; 705 fireChangeEvent(); 706 } 707 708 /** 709 * Returns the lower and upper bounds (range) of the x-values in the 710 * specified dataset. 711 * 712 * @param dataset the dataset (<code>null</code> permitted). 713 * 714 * @return The range (<code>null</code> if the dataset is <code>null</code> 715 * or empty). 716 * 717 * @see #findRangeBounds(XYDataset) 718 */ 719 public Range findDomainBounds(XYDataset dataset) { 720 if (dataset != null) { 721 return DatasetUtilities.findDomainBounds(dataset, false); 722 } 723 else { 724 return null; 725 } 726 } 727 728 /** 729 * Returns the range of values the renderer requires to display all the 730 * items from the specified dataset. 731 * 732 * @param dataset the dataset (<code>null</code> permitted). 733 * 734 * @return The range (<code>null</code> if the dataset is <code>null</code> 735 * or empty). 736 * 737 * @see #findDomainBounds(XYDataset) 738 */ 739 public Range findRangeBounds(XYDataset dataset) { 740 if (dataset != null) { 741 return DatasetUtilities.findRangeBounds(dataset, false); 742 } 743 else { 744 return null; 745 } 746 } 747 748 /** 749 * Returns a (possibly empty) collection of legend items for the series 750 * that this renderer is responsible for drawing. 751 * 752 * @return The legend item collection (never <code>null</code>). 753 */ 754 public LegendItemCollection getLegendItems() { 755 if (this.plot == null) { 756 return new LegendItemCollection(); 757 } 758 LegendItemCollection result = new LegendItemCollection(); 759 int index = this.plot.getIndexOf(this); 760 XYDataset dataset = this.plot.getDataset(index); 761 if (dataset != null) { 762 int seriesCount = dataset.getSeriesCount(); 763 for (int i = 0; i < seriesCount; i++) { 764 if (isSeriesVisibleInLegend(i)) { 765 LegendItem item = getLegendItem(index, i); 766 if (item != null) { 767 result.add(item); 768 } 769 } 770 } 771 772 } 773 return result; 774 } 775 776 /** 777 * Returns a default legend item for the specified series. Subclasses 778 * should override this method to generate customised items. 779 * 780 * @param datasetIndex the dataset index (zero-based). 781 * @param series the series index (zero-based). 782 * 783 * @return A legend item for the series. 784 */ 785 public LegendItem getLegendItem(int datasetIndex, int series) { 786 LegendItem result = null; 787 XYPlot xyplot = getPlot(); 788 if (xyplot != null) { 789 XYDataset dataset = xyplot.getDataset(datasetIndex); 790 if (dataset != null) { 791 String label = this.legendItemLabelGenerator.generateLabel( 792 dataset, series); 793 String description = label; 794 String toolTipText = null; 795 if (getLegendItemToolTipGenerator() != null) { 796 toolTipText = getLegendItemToolTipGenerator().generateLabel( 797 dataset, series); 798 } 799 String urlText = null; 800 if (getLegendItemURLGenerator() != null) { 801 urlText = getLegendItemURLGenerator().generateLabel( 802 dataset, series); 803 } 804 Shape shape = lookupLegendShape(series); 805 Paint paint = lookupSeriesPaint(series); 806 Paint outlinePaint = lookupSeriesOutlinePaint(series); 807 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 808 result = new LegendItem(label, description, toolTipText, 809 urlText, shape, paint, outlineStroke, outlinePaint); 810 Paint labelPaint = lookupLegendTextPaint(series); 811 result.setLabelFont(lookupLegendTextFont(series)); 812 if (labelPaint != null) { 813 result.setLabelPaint(labelPaint); 814 } 815 result.setSeriesKey(dataset.getSeriesKey(series)); 816 result.setSeriesIndex(series); 817 result.setDataset(dataset); 818 result.setDatasetIndex(datasetIndex); 819 } 820 } 821 return result; 822 } 823 824 /** 825 * Fills a band between two values on the axis. This can be used to color 826 * bands between the grid lines. 827 * 828 * @param g2 the graphics device. 829 * @param plot the plot. 830 * @param axis the domain axis. 831 * @param dataArea the data area. 832 * @param start the start value. 833 * @param end the end value. 834 */ 835 public void fillDomainGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis, 836 Rectangle2D dataArea, double start, double end) { 837 838 double x1 = axis.valueToJava2D(start, dataArea, 839 plot.getDomainAxisEdge()); 840 double x2 = axis.valueToJava2D(end, dataArea, 841 plot.getDomainAxisEdge()); 842 Rectangle2D band; 843 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 844 band = new Rectangle2D.Double(Math.min(x1, x2), dataArea.getMinY(), 845 Math.abs(x2 - x1), dataArea.getWidth()); 846 } 847 else { 848 band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(x1, x2), 849 dataArea.getWidth(), Math.abs(x2 - x1)); 850 } 851 Paint paint = plot.getDomainTickBandPaint(); 852 853 if (paint != null) { 854 g2.setPaint(paint); 855 g2.fill(band); 856 } 857 858 } 859 860 /** 861 * Fills a band between two values on the range axis. This can be used to 862 * color bands between the grid lines. 863 * 864 * @param g2 the graphics device. 865 * @param plot the plot. 866 * @param axis the range axis. 867 * @param dataArea the data area. 868 * @param start the start value. 869 * @param end the end value. 870 */ 871 public void fillRangeGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis, 872 Rectangle2D dataArea, double start, double end) { 873 874 double y1 = axis.valueToJava2D(start, dataArea, 875 plot.getRangeAxisEdge()); 876 double y2 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge()); 877 Rectangle2D band; 878 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 879 band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(y1, y2), 880 dataArea.getWidth(), Math.abs(y2 - y1)); 881 } 882 else { 883 band = new Rectangle2D.Double(Math.min(y1, y2), dataArea.getMinY(), 884 Math.abs(y2 - y1), dataArea.getHeight()); 885 } 886 Paint paint = plot.getRangeTickBandPaint(); 887 888 if (paint != null) { 889 g2.setPaint(paint); 890 g2.fill(band); 891 } 892 893 } 894 895 /** 896 * Draws a grid line against the range axis. 897 * 898 * @param g2 the graphics device. 899 * @param plot the plot. 900 * @param axis the value axis. 901 * @param dataArea the area for plotting data (not yet adjusted for any 902 * 3D effect). 903 * @param value the value at which the grid line should be drawn. 904 */ 905 public void drawDomainGridLine(Graphics2D g2, 906 XYPlot plot, 907 ValueAxis axis, 908 Rectangle2D dataArea, 909 double value) { 910 911 Range range = axis.getRange(); 912 if (!range.contains(value)) { 913 return; 914 } 915 916 PlotOrientation orientation = plot.getOrientation(); 917 double v = axis.valueToJava2D(value, dataArea, 918 plot.getDomainAxisEdge()); 919 Line2D line = null; 920 if (orientation == PlotOrientation.HORIZONTAL) { 921 line = new Line2D.Double(dataArea.getMinX(), v, 922 dataArea.getMaxX(), v); 923 } 924 else if (orientation == PlotOrientation.VERTICAL) { 925 line = new Line2D.Double(v, dataArea.getMinY(), v, 926 dataArea.getMaxY()); 927 } 928 929 Paint paint = plot.getDomainGridlinePaint(); 930 Stroke stroke = plot.getDomainGridlineStroke(); 931 g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT); 932 g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE); 933 g2.draw(line); 934 935 } 936 937 /** 938 * Draws a line perpendicular to the domain axis. 939 * 940 * @param g2 the graphics device. 941 * @param plot the plot. 942 * @param axis the value axis. 943 * @param dataArea the area for plotting data (not yet adjusted for any 3D 944 * effect). 945 * @param value the value at which the grid line should be drawn. 946 * @param paint the paint (<code>null</code> not permitted). 947 * @param stroke the stroke (<code>null</code> not permitted). 948 * 949 * @since 1.0.5 950 */ 951 public void drawDomainLine(Graphics2D g2, XYPlot plot, ValueAxis axis, 952 Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { 953 954 Range range = axis.getRange(); 955 if (!range.contains(value)) { 956 return; 957 } 958 959 PlotOrientation orientation = plot.getOrientation(); 960 Line2D line = null; 961 double v = axis.valueToJava2D(value, dataArea, 962 plot.getDomainAxisEdge()); 963 if (orientation == PlotOrientation.HORIZONTAL) { 964 line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), 965 v); 966 } 967 else if (orientation == PlotOrientation.VERTICAL) { 968 line = new Line2D.Double(v, dataArea.getMinY(), v, 969 dataArea.getMaxY()); 970 } 971 972 g2.setPaint(paint); 973 g2.setStroke(stroke); 974 g2.draw(line); 975 976 } 977 978 /** 979 * Draws a line perpendicular to the range axis. 980 * 981 * @param g2 the graphics device. 982 * @param plot the plot. 983 * @param axis the value axis. 984 * @param dataArea the area for plotting data (not yet adjusted for any 3D 985 * effect). 986 * @param value the value at which the grid line should be drawn. 987 * @param paint the paint. 988 * @param stroke the stroke. 989 */ 990 public void drawRangeLine(Graphics2D g2, 991 XYPlot plot, 992 ValueAxis axis, 993 Rectangle2D dataArea, 994 double value, 995 Paint paint, 996 Stroke stroke) { 997 998 Range range = axis.getRange(); 999 if (!range.contains(value)) { 1000 return; 1001 } 1002 1003 PlotOrientation orientation = plot.getOrientation(); 1004 Line2D line = null; 1005 double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge()); 1006 if (orientation == PlotOrientation.HORIZONTAL) { 1007 line = new Line2D.Double(v, dataArea.getMinY(), v, 1008 dataArea.getMaxY()); 1009 } 1010 else if (orientation == PlotOrientation.VERTICAL) { 1011 line = new Line2D.Double(dataArea.getMinX(), v, 1012 dataArea.getMaxX(), v); 1013 } 1014 1015 g2.setPaint(paint); 1016 g2.setStroke(stroke); 1017 g2.draw(line); 1018 1019 } 1020 1021 /** 1022 * Draws a vertical line on the chart to represent a 'range marker'. 1023 * 1024 * @param g2 the graphics device. 1025 * @param plot the plot. 1026 * @param domainAxis the domain axis. 1027 * @param marker the marker line. 1028 * @param dataArea the axis data area. 1029 */ 1030 public void drawDomainMarker(Graphics2D g2, 1031 XYPlot plot, 1032 ValueAxis domainAxis, 1033 Marker marker, 1034 Rectangle2D dataArea) { 1035 1036 if (marker instanceof ValueMarker) { 1037 ValueMarker vm = (ValueMarker) marker; 1038 double value = vm.getValue(); 1039 Range range = domainAxis.getRange(); 1040 if (!range.contains(value)) { 1041 return; 1042 } 1043 1044 double v = domainAxis.valueToJava2D(value, dataArea, 1045 plot.getDomainAxisEdge()); 1046 1047 PlotOrientation orientation = plot.getOrientation(); 1048 Line2D line = null; 1049 if (orientation == PlotOrientation.HORIZONTAL) { 1050 line = new Line2D.Double(dataArea.getMinX(), v, 1051 dataArea.getMaxX(), v); 1052 } 1053 else if (orientation == PlotOrientation.VERTICAL) { 1054 line = new Line2D.Double(v, dataArea.getMinY(), v, 1055 dataArea.getMaxY()); 1056 } 1057 1058 final Composite originalComposite = g2.getComposite(); 1059 g2.setComposite(AlphaComposite.getInstance( 1060 AlphaComposite.SRC_OVER, marker.getAlpha())); 1061 g2.setPaint(marker.getPaint()); 1062 g2.setStroke(marker.getStroke()); 1063 g2.draw(line); 1064 1065 String label = marker.getLabel(); 1066 RectangleAnchor anchor = marker.getLabelAnchor(); 1067 if (label != null) { 1068 Font labelFont = marker.getLabelFont(); 1069 g2.setFont(labelFont); 1070 g2.setPaint(marker.getLabelPaint()); 1071 Point2D coordinates = calculateDomainMarkerTextAnchorPoint( 1072 g2, orientation, dataArea, line.getBounds2D(), 1073 marker.getLabelOffset(), 1074 LengthAdjustmentType.EXPAND, anchor); 1075 TextUtilities.drawAlignedString(label, g2, 1076 (float) coordinates.getX(), (float) coordinates.getY(), 1077 marker.getLabelTextAnchor()); 1078 } 1079 g2.setComposite(originalComposite); 1080 } 1081 else if (marker instanceof IntervalMarker) { 1082 IntervalMarker im = (IntervalMarker) marker; 1083 double start = im.getStartValue(); 1084 double end = im.getEndValue(); 1085 Range range = domainAxis.getRange(); 1086 if (!(range.intersects(start, end))) { 1087 return; 1088 } 1089 1090 double start2d = domainAxis.valueToJava2D(start, dataArea, 1091 plot.getDomainAxisEdge()); 1092 double end2d = domainAxis.valueToJava2D(end, dataArea, 1093 plot.getDomainAxisEdge()); 1094 double low = Math.min(start2d, end2d); 1095 double high = Math.max(start2d, end2d); 1096 1097 PlotOrientation orientation = plot.getOrientation(); 1098 Rectangle2D rect = null; 1099 if (orientation == PlotOrientation.HORIZONTAL) { 1100 // clip top and bottom bounds to data area 1101 low = Math.max(low, dataArea.getMinY()); 1102 high = Math.min(high, dataArea.getMaxY()); 1103 rect = new Rectangle2D.Double(dataArea.getMinX(), 1104 low, dataArea.getWidth(), 1105 high - low); 1106 } 1107 else if (orientation == PlotOrientation.VERTICAL) { 1108 // clip left and right bounds to data area 1109 low = Math.max(low, dataArea.getMinX()); 1110 high = Math.min(high, dataArea.getMaxX()); 1111 rect = new Rectangle2D.Double(low, 1112 dataArea.getMinY(), high - low, 1113 dataArea.getHeight()); 1114 } 1115 1116 final Composite originalComposite = g2.getComposite(); 1117 g2.setComposite(AlphaComposite.getInstance( 1118 AlphaComposite.SRC_OVER, marker.getAlpha())); 1119 Paint p = marker.getPaint(); 1120 if (p instanceof GradientPaint) { 1121 GradientPaint gp = (GradientPaint) p; 1122 GradientPaintTransformer t = im.getGradientPaintTransformer(); 1123 if (t != null) { 1124 gp = t.transform(gp, rect); 1125 } 1126 g2.setPaint(gp); 1127 } 1128 else { 1129 g2.setPaint(p); 1130 } 1131 g2.fill(rect); 1132 1133 // now draw the outlines, if visible... 1134 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { 1135 if (orientation == PlotOrientation.VERTICAL) { 1136 Line2D line = new Line2D.Double(); 1137 double y0 = dataArea.getMinY(); 1138 double y1 = dataArea.getMaxY(); 1139 g2.setPaint(im.getOutlinePaint()); 1140 g2.setStroke(im.getOutlineStroke()); 1141 if (range.contains(start)) { 1142 line.setLine(start2d, y0, start2d, y1); 1143 g2.draw(line); 1144 } 1145 if (range.contains(end)) { 1146 line.setLine(end2d, y0, end2d, y1); 1147 g2.draw(line); 1148 } 1149 } 1150 else { // PlotOrientation.HORIZONTAL 1151 Line2D line = new Line2D.Double(); 1152 double x0 = dataArea.getMinX(); 1153 double x1 = dataArea.getMaxX(); 1154 g2.setPaint(im.getOutlinePaint()); 1155 g2.setStroke(im.getOutlineStroke()); 1156 if (range.contains(start)) { 1157 line.setLine(x0, start2d, x1, start2d); 1158 g2.draw(line); 1159 } 1160 if (range.contains(end)) { 1161 line.setLine(x0, end2d, x1, end2d); 1162 g2.draw(line); 1163 } 1164 } 1165 } 1166 1167 String label = marker.getLabel(); 1168 RectangleAnchor anchor = marker.getLabelAnchor(); 1169 if (label != null) { 1170 Font labelFont = marker.getLabelFont(); 1171 g2.setFont(labelFont); 1172 g2.setPaint(marker.getLabelPaint()); 1173 Point2D coordinates = calculateDomainMarkerTextAnchorPoint( 1174 g2, orientation, dataArea, rect, 1175 marker.getLabelOffset(), marker.getLabelOffsetType(), 1176 anchor); 1177 TextUtilities.drawAlignedString(label, g2, 1178 (float) coordinates.getX(), (float) coordinates.getY(), 1179 marker.getLabelTextAnchor()); 1180 } 1181 g2.setComposite(originalComposite); 1182 1183 } 1184 1185 } 1186 1187 /** 1188 * Calculates the (x, y) coordinates for drawing a marker label. 1189 * 1190 * @param g2 the graphics device. 1191 * @param orientation the plot orientation. 1192 * @param dataArea the data area. 1193 * @param markerArea the rectangle surrounding the marker area. 1194 * @param markerOffset the marker label offset. 1195 * @param labelOffsetType the label offset type. 1196 * @param anchor the label anchor. 1197 * 1198 * @return The coordinates for drawing the marker label. 1199 */ 1200 protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2, 1201 PlotOrientation orientation, 1202 Rectangle2D dataArea, 1203 Rectangle2D markerArea, 1204 RectangleInsets markerOffset, 1205 LengthAdjustmentType labelOffsetType, 1206 RectangleAnchor anchor) { 1207 1208 Rectangle2D anchorRect = null; 1209 if (orientation == PlotOrientation.HORIZONTAL) { 1210 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1211 LengthAdjustmentType.CONTRACT, labelOffsetType); 1212 } 1213 else if (orientation == PlotOrientation.VERTICAL) { 1214 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1215 labelOffsetType, LengthAdjustmentType.CONTRACT); 1216 } 1217 return RectangleAnchor.coordinates(anchorRect, anchor); 1218 1219 } 1220 1221 /** 1222 * Draws a horizontal line across the chart to represent a 'range marker'. 1223 * 1224 * @param g2 the graphics device. 1225 * @param plot the plot. 1226 * @param rangeAxis the range axis. 1227 * @param marker the marker line. 1228 * @param dataArea the axis data area. 1229 */ 1230 public void drawRangeMarker(Graphics2D g2, 1231 XYPlot plot, 1232 ValueAxis rangeAxis, 1233 Marker marker, 1234 Rectangle2D dataArea) { 1235 1236 if (marker instanceof ValueMarker) { 1237 ValueMarker vm = (ValueMarker) marker; 1238 double value = vm.getValue(); 1239 Range range = rangeAxis.getRange(); 1240 if (!range.contains(value)) { 1241 return; 1242 } 1243 1244 double v = rangeAxis.valueToJava2D(value, dataArea, 1245 plot.getRangeAxisEdge()); 1246 PlotOrientation orientation = plot.getOrientation(); 1247 Line2D line = null; 1248 if (orientation == PlotOrientation.HORIZONTAL) { 1249 line = new Line2D.Double(v, dataArea.getMinY(), v, 1250 dataArea.getMaxY()); 1251 } 1252 else if (orientation == PlotOrientation.VERTICAL) { 1253 line = new Line2D.Double(dataArea.getMinX(), v, 1254 dataArea.getMaxX(), v); 1255 } 1256 1257 final Composite originalComposite = g2.getComposite(); 1258 g2.setComposite(AlphaComposite.getInstance( 1259 AlphaComposite.SRC_OVER, marker.getAlpha())); 1260 g2.setPaint(marker.getPaint()); 1261 g2.setStroke(marker.getStroke()); 1262 g2.draw(line); 1263 1264 String label = marker.getLabel(); 1265 RectangleAnchor anchor = marker.getLabelAnchor(); 1266 if (label != null) { 1267 Font labelFont = marker.getLabelFont(); 1268 g2.setFont(labelFont); 1269 g2.setPaint(marker.getLabelPaint()); 1270 Point2D coordinates = calculateRangeMarkerTextAnchorPoint( 1271 g2, orientation, dataArea, line.getBounds2D(), 1272 marker.getLabelOffset(), 1273 LengthAdjustmentType.EXPAND, anchor); 1274 TextUtilities.drawAlignedString(label, g2, 1275 (float) coordinates.getX(), (float) coordinates.getY(), 1276 marker.getLabelTextAnchor()); 1277 } 1278 g2.setComposite(originalComposite); 1279 } 1280 else if (marker instanceof IntervalMarker) { 1281 IntervalMarker im = (IntervalMarker) marker; 1282 double start = im.getStartValue(); 1283 double end = im.getEndValue(); 1284 Range range = rangeAxis.getRange(); 1285 if (!(range.intersects(start, end))) { 1286 return; 1287 } 1288 1289 double start2d = rangeAxis.valueToJava2D(start, dataArea, 1290 plot.getRangeAxisEdge()); 1291 double end2d = rangeAxis.valueToJava2D(end, dataArea, 1292 plot.getRangeAxisEdge()); 1293 double low = Math.min(start2d, end2d); 1294 double high = Math.max(start2d, end2d); 1295 1296 PlotOrientation orientation = plot.getOrientation(); 1297 Rectangle2D rect = null; 1298 if (orientation == PlotOrientation.HORIZONTAL) { 1299 // clip left and right bounds to data area 1300 low = Math.max(low, dataArea.getMinX()); 1301 high = Math.min(high, dataArea.getMaxX()); 1302 rect = new Rectangle2D.Double(low, 1303 dataArea.getMinY(), high - low, 1304 dataArea.getHeight()); 1305 } 1306 else if (orientation == PlotOrientation.VERTICAL) { 1307 // clip top and bottom bounds to data area 1308 low = Math.max(low, dataArea.getMinY()); 1309 high = Math.min(high, dataArea.getMaxY()); 1310 rect = new Rectangle2D.Double(dataArea.getMinX(), 1311 low, dataArea.getWidth(), 1312 high - low); 1313 } 1314 1315 final Composite originalComposite = g2.getComposite(); 1316 g2.setComposite(AlphaComposite.getInstance( 1317 AlphaComposite.SRC_OVER, marker.getAlpha())); 1318 Paint p = marker.getPaint(); 1319 if (p instanceof GradientPaint) { 1320 GradientPaint gp = (GradientPaint) p; 1321 GradientPaintTransformer t = im.getGradientPaintTransformer(); 1322 if (t != null) { 1323 gp = t.transform(gp, rect); 1324 } 1325 g2.setPaint(gp); 1326 } 1327 else { 1328 g2.setPaint(p); 1329 } 1330 g2.fill(rect); 1331 1332 // now draw the outlines, if visible... 1333 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { 1334 if (orientation == PlotOrientation.VERTICAL) { 1335 Line2D line = new Line2D.Double(); 1336 double x0 = dataArea.getMinX(); 1337 double x1 = dataArea.getMaxX(); 1338 g2.setPaint(im.getOutlinePaint()); 1339 g2.setStroke(im.getOutlineStroke()); 1340 if (range.contains(start)) { 1341 line.setLine(x0, start2d, x1, start2d); 1342 g2.draw(line); 1343 } 1344 if (range.contains(end)) { 1345 line.setLine(x0, end2d, x1, end2d); 1346 g2.draw(line); 1347 } 1348 } 1349 else { // PlotOrientation.HORIZONTAL 1350 Line2D line = new Line2D.Double(); 1351 double y0 = dataArea.getMinY(); 1352 double y1 = dataArea.getMaxY(); 1353 g2.setPaint(im.getOutlinePaint()); 1354 g2.setStroke(im.getOutlineStroke()); 1355 if (range.contains(start)) { 1356 line.setLine(start2d, y0, start2d, y1); 1357 g2.draw(line); 1358 } 1359 if (range.contains(end)) { 1360 line.setLine(end2d, y0, end2d, y1); 1361 g2.draw(line); 1362 } 1363 } 1364 } 1365 1366 String label = marker.getLabel(); 1367 RectangleAnchor anchor = marker.getLabelAnchor(); 1368 if (label != null) { 1369 Font labelFont = marker.getLabelFont(); 1370 g2.setFont(labelFont); 1371 g2.setPaint(marker.getLabelPaint()); 1372 Point2D coordinates = calculateRangeMarkerTextAnchorPoint( 1373 g2, orientation, dataArea, rect, 1374 marker.getLabelOffset(), marker.getLabelOffsetType(), 1375 anchor); 1376 TextUtilities.drawAlignedString(label, g2, 1377 (float) coordinates.getX(), (float) coordinates.getY(), 1378 marker.getLabelTextAnchor()); 1379 } 1380 g2.setComposite(originalComposite); 1381 } 1382 } 1383 1384 /** 1385 * Calculates the (x, y) coordinates for drawing a marker label. 1386 * 1387 * @param g2 the graphics device. 1388 * @param orientation the plot orientation. 1389 * @param dataArea the data area. 1390 * @param markerArea the marker area. 1391 * @param markerOffset the marker offset. 1392 * @param labelOffsetForRange ?? 1393 * @param anchor the label anchor. 1394 * 1395 * @return The coordinates for drawing the marker label. 1396 */ 1397 private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2, 1398 PlotOrientation orientation, 1399 Rectangle2D dataArea, 1400 Rectangle2D markerArea, 1401 RectangleInsets markerOffset, 1402 LengthAdjustmentType labelOffsetForRange, 1403 RectangleAnchor anchor) { 1404 1405 Rectangle2D anchorRect = null; 1406 if (orientation == PlotOrientation.HORIZONTAL) { 1407 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1408 labelOffsetForRange, LengthAdjustmentType.CONTRACT); 1409 } 1410 else if (orientation == PlotOrientation.VERTICAL) { 1411 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1412 LengthAdjustmentType.CONTRACT, labelOffsetForRange); 1413 } 1414 return RectangleAnchor.coordinates(anchorRect, anchor); 1415 1416 } 1417 1418 /** 1419 * Returns a clone of the renderer. 1420 * 1421 * @return A clone. 1422 * 1423 * @throws CloneNotSupportedException if the renderer does not support 1424 * cloning. 1425 */ 1426 protected Object clone() throws CloneNotSupportedException { 1427 AbstractXYItemRenderer clone = (AbstractXYItemRenderer) super.clone(); 1428 // 'plot' : just retain reference, not a deep copy 1429 1430 if (this.itemLabelGenerator != null 1431 && this.itemLabelGenerator instanceof PublicCloneable) { 1432 PublicCloneable pc = (PublicCloneable) this.itemLabelGenerator; 1433 clone.itemLabelGenerator = (XYItemLabelGenerator) pc.clone(); 1434 } 1435 clone.itemLabelGeneratorList 1436 = (ObjectList) this.itemLabelGeneratorList.clone(); 1437 if (this.baseItemLabelGenerator != null 1438 && this.baseItemLabelGenerator instanceof PublicCloneable) { 1439 PublicCloneable pc = (PublicCloneable) this.baseItemLabelGenerator; 1440 clone.baseItemLabelGenerator = (XYItemLabelGenerator) pc.clone(); 1441 } 1442 1443 if (this.toolTipGenerator != null 1444 && this.toolTipGenerator instanceof PublicCloneable) { 1445 PublicCloneable pc = (PublicCloneable) this.toolTipGenerator; 1446 clone.toolTipGenerator = (XYToolTipGenerator) pc.clone(); 1447 } 1448 clone.toolTipGeneratorList 1449 = (ObjectList) this.toolTipGeneratorList.clone(); 1450 if (this.baseToolTipGenerator != null 1451 && this.baseToolTipGenerator instanceof PublicCloneable) { 1452 PublicCloneable pc = (PublicCloneable) this.baseToolTipGenerator; 1453 clone.baseToolTipGenerator = (XYToolTipGenerator) pc.clone(); 1454 } 1455 1456 if (clone.legendItemLabelGenerator instanceof PublicCloneable) { 1457 clone.legendItemLabelGenerator = (XYSeriesLabelGenerator) 1458 ObjectUtilities.clone(this.legendItemLabelGenerator); 1459 } 1460 if (clone.legendItemToolTipGenerator instanceof PublicCloneable) { 1461 clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) 1462 ObjectUtilities.clone(this.legendItemToolTipGenerator); 1463 } 1464 if (clone.legendItemURLGenerator instanceof PublicCloneable) { 1465 clone.legendItemURLGenerator = (XYSeriesLabelGenerator) 1466 ObjectUtilities.clone(this.legendItemURLGenerator); 1467 } 1468 1469 clone.foregroundAnnotations = (List) ObjectUtilities.deepClone( 1470 this.foregroundAnnotations); 1471 clone.backgroundAnnotations = (List) ObjectUtilities.deepClone( 1472 this.backgroundAnnotations); 1473 1474 if (clone.legendItemLabelGenerator instanceof PublicCloneable) { 1475 clone.legendItemLabelGenerator = (XYSeriesLabelGenerator) 1476 ObjectUtilities.clone(this.legendItemLabelGenerator); 1477 } 1478 if (clone.legendItemToolTipGenerator instanceof PublicCloneable) { 1479 clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) 1480 ObjectUtilities.clone(this.legendItemToolTipGenerator); 1481 } 1482 if (clone.legendItemURLGenerator instanceof PublicCloneable) { 1483 clone.legendItemURLGenerator = (XYSeriesLabelGenerator) 1484 ObjectUtilities.clone(this.legendItemURLGenerator); 1485 } 1486 1487 return clone; 1488 } 1489 1490 /** 1491 * Tests this renderer for equality with another object. 1492 * 1493 * @param obj the object (<code>null</code> permitted). 1494 * 1495 * @return <code>true</code> or <code>false</code>. 1496 */ 1497 public boolean equals(Object obj) { 1498 if (obj == this) { 1499 return true; 1500 } 1501 if (!(obj instanceof AbstractXYItemRenderer)) { 1502 return false; 1503 } 1504 AbstractXYItemRenderer that = (AbstractXYItemRenderer) obj; 1505 if (!ObjectUtilities.equal(this.itemLabelGenerator, 1506 that.itemLabelGenerator)) { 1507 return false; 1508 } 1509 if (!this.itemLabelGeneratorList.equals(that.itemLabelGeneratorList)) { 1510 return false; 1511 } 1512 if (!ObjectUtilities.equal(this.baseItemLabelGenerator, 1513 that.baseItemLabelGenerator)) { 1514 return false; 1515 } 1516 if (!ObjectUtilities.equal(this.toolTipGenerator, 1517 that.toolTipGenerator)) { 1518 return false; 1519 } 1520 if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) { 1521 return false; 1522 } 1523 if (!ObjectUtilities.equal(this.baseToolTipGenerator, 1524 that.baseToolTipGenerator)) { 1525 return false; 1526 } 1527 if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) { 1528 return false; 1529 } 1530 if (!this.foregroundAnnotations.equals(that.foregroundAnnotations)) { 1531 return false; 1532 } 1533 if (!this.backgroundAnnotations.equals(that.backgroundAnnotations)) { 1534 return false; 1535 } 1536 if (this.defaultEntityRadius != that.defaultEntityRadius) { 1537 return false; 1538 } 1539 if (!ObjectUtilities.equal(this.legendItemLabelGenerator, 1540 that.legendItemLabelGenerator)) { 1541 return false; 1542 } 1543 if (!ObjectUtilities.equal(this.legendItemToolTipGenerator, 1544 that.legendItemToolTipGenerator)) { 1545 return false; 1546 } 1547 if (!ObjectUtilities.equal(this.legendItemURLGenerator, 1548 that.legendItemURLGenerator)) { 1549 return false; 1550 } 1551 return super.equals(obj); 1552 } 1553 1554 /** 1555 * Returns the drawing supplier from the plot. 1556 * 1557 * @return The drawing supplier (possibly <code>null</code>). 1558 */ 1559 public DrawingSupplier getDrawingSupplier() { 1560 DrawingSupplier result = null; 1561 XYPlot p = getPlot(); 1562 if (p != null) { 1563 result = p.getDrawingSupplier(); 1564 } 1565 return result; 1566 } 1567 1568 /** 1569 * Considers the current (x, y) coordinate and updates the crosshair point 1570 * if it meets the criteria (usually means the (x, y) coordinate is the 1571 * closest to the anchor point so far). 1572 * 1573 * @param crosshairState the crosshair state (<code>null</code> permitted, 1574 * but the method does nothing in that case). 1575 * @param x the x-value (in data space). 1576 * @param y the y-value (in data space). 1577 * @param transX the x-value translated to Java2D space. 1578 * @param transY the y-value translated to Java2D space. 1579 * @param orientation the plot orientation (<code>null</code> not 1580 * permitted). 1581 * 1582 * @deprecated Use {@link #updateCrosshairValues(CrosshairState, double, 1583 * double, int, int, double, double, PlotOrientation)} -- see bug 1584 * report 1086307. 1585 */ 1586 protected void updateCrosshairValues(CrosshairState crosshairState, 1587 double x, double y, double transX, double transY, 1588 PlotOrientation orientation) { 1589 updateCrosshairValues(crosshairState, x, y, 0, 0, transX, transY, 1590 orientation); 1591 } 1592 1593 /** 1594 * Considers the current (x, y) coordinate and updates the crosshair point 1595 * if it meets the criteria (usually means the (x, y) coordinate is the 1596 * closest to the anchor point so far). 1597 * 1598 * @param crosshairState the crosshair state (<code>null</code> permitted, 1599 * but the method does nothing in that case). 1600 * @param x the x-value (in data space). 1601 * @param y the y-value (in data space). 1602 * @param domainAxisIndex the index of the domain axis for the point. 1603 * @param rangeAxisIndex the index of the range axis for the point. 1604 * @param transX the x-value translated to Java2D space. 1605 * @param transY the y-value translated to Java2D space. 1606 * @param orientation the plot orientation (<code>null</code> not 1607 * permitted). 1608 * 1609 * @since 1.0.4 1610 */ 1611 protected void updateCrosshairValues(CrosshairState crosshairState, 1612 double x, double y, int domainAxisIndex, int rangeAxisIndex, 1613 double transX, double transY, PlotOrientation orientation) { 1614 1615 if (orientation == null) { 1616 throw new IllegalArgumentException("Null 'orientation' argument."); 1617 } 1618 1619 if (crosshairState != null) { 1620 // do we need to update the crosshair values? 1621 if (this.plot.isDomainCrosshairLockedOnData()) { 1622 if (this.plot.isRangeCrosshairLockedOnData()) { 1623 // both axes 1624 crosshairState.updateCrosshairPoint(x, y, domainAxisIndex, 1625 rangeAxisIndex, transX, transY, orientation); 1626 } 1627 else { 1628 // just the domain axis... 1629 crosshairState.updateCrosshairX(x, domainAxisIndex); 1630 } 1631 } 1632 else { 1633 if (this.plot.isRangeCrosshairLockedOnData()) { 1634 // just the range axis... 1635 crosshairState.updateCrosshairY(y, rangeAxisIndex); 1636 } 1637 } 1638 } 1639 1640 } 1641 1642 /** 1643 * Draws an item label. 1644 * 1645 * @param g2 the graphics device. 1646 * @param orientation the orientation. 1647 * @param dataset the dataset. 1648 * @param series the series index (zero-based). 1649 * @param item the item index (zero-based). 1650 * @param x the x coordinate (in Java2D space). 1651 * @param y the y coordinate (in Java2D space). 1652 * @param negative indicates a negative value (which affects the item 1653 * label position). 1654 */ 1655 protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation, 1656 XYDataset dataset, int series, int item, double x, double y, 1657 boolean negative) { 1658 1659 XYItemLabelGenerator generator = getItemLabelGenerator(series, item); 1660 if (generator != null) { 1661 Font labelFont = getItemLabelFont(series, item); 1662 Paint paint = getItemLabelPaint(series, item); 1663 g2.setFont(labelFont); 1664 g2.setPaint(paint); 1665 String label = generator.generateLabel(dataset, series, item); 1666 1667 // get the label position.. 1668 ItemLabelPosition position = null; 1669 if (!negative) { 1670 position = getPositiveItemLabelPosition(series, item); 1671 } 1672 else { 1673 position = getNegativeItemLabelPosition(series, item); 1674 } 1675 1676 // work out the label anchor point... 1677 Point2D anchorPoint = calculateLabelAnchorPoint( 1678 position.getItemLabelAnchor(), x, y, orientation); 1679 TextUtilities.drawRotatedString(label, g2, 1680 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 1681 position.getTextAnchor(), position.getAngle(), 1682 position.getRotationAnchor()); 1683 } 1684 1685 } 1686 1687 /** 1688 * Draws all the annotations for the specified layer. 1689 * 1690 * @param g2 the graphics device. 1691 * @param dataArea the data area. 1692 * @param domainAxis the domain axis. 1693 * @param rangeAxis the range axis. 1694 * @param layer the layer. 1695 * @param info the plot rendering info. 1696 */ 1697 public void drawAnnotations(Graphics2D g2, 1698 Rectangle2D dataArea, 1699 ValueAxis domainAxis, 1700 ValueAxis rangeAxis, 1701 Layer layer, 1702 PlotRenderingInfo info) { 1703 1704 Iterator iterator = null; 1705 if (layer.equals(Layer.FOREGROUND)) { 1706 iterator = this.foregroundAnnotations.iterator(); 1707 } 1708 else if (layer.equals(Layer.BACKGROUND)) { 1709 iterator = this.backgroundAnnotations.iterator(); 1710 } 1711 else { 1712 // should not get here 1713 throw new RuntimeException("Unknown layer."); 1714 } 1715 while (iterator.hasNext()) { 1716 XYAnnotation annotation = (XYAnnotation) iterator.next(); 1717 annotation.draw(g2, this.plot, dataArea, domainAxis, rangeAxis, 1718 0, info); 1719 } 1720 1721 } 1722 1723 /** 1724 * Adds an entity to the collection. 1725 * 1726 * @param entities the entity collection being populated. 1727 * @param area the entity area (if <code>null</code> a default will be 1728 * used). 1729 * @param dataset the dataset. 1730 * @param series the series. 1731 * @param item the item. 1732 * @param entityX the entity's center x-coordinate in user space (only 1733 * used if <code>area</code> is <code>null</code>). 1734 * @param entityY the entity's center y-coordinate in user space (only 1735 * used if <code>area</code> is <code>null</code>). 1736 */ 1737 protected void addEntity(EntityCollection entities, Shape area, 1738 XYDataset dataset, int series, int item, 1739 double entityX, double entityY) { 1740 if (!getItemCreateEntity(series, item)) { 1741 return; 1742 } 1743 Shape hotspot = area; 1744 if (hotspot == null) { 1745 double w = this.defaultEntityRadius * 2; 1746 if (getPlot().getOrientation() == PlotOrientation.VERTICAL) { 1747 hotspot = new Ellipse2D.Double( 1748 entityX - this.defaultEntityRadius, 1749 entityY - this.defaultEntityRadius, w, w); 1750 } 1751 else { 1752 hotspot = new Ellipse2D.Double( 1753 entityY - this.defaultEntityRadius, 1754 entityX - this.defaultEntityRadius, w, w); 1755 } 1756 } 1757 String tip = null; 1758 XYToolTipGenerator generator = getToolTipGenerator(series, item); 1759 if (generator != null) { 1760 tip = generator.generateToolTip(dataset, series, item); 1761 } 1762 String url = null; 1763 if (getURLGenerator() != null) { 1764 url = getURLGenerator().generateURL(dataset, series, item); 1765 } 1766 XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, 1767 tip, url); 1768 entities.add(entity); 1769 } 1770 1771 /** 1772 * Returns <code>true</code> if the specified point (x, y) falls within or 1773 * on the boundary of the specified rectangle. 1774 * 1775 * @param rect the rectangle (<code>null</code> not permitted). 1776 * @param x the x-coordinate. 1777 * @param y the y-coordinate. 1778 * 1779 * @return A boolean. 1780 * 1781 * @since 1.0.10 1782 */ 1783 public static boolean isPointInRect(Rectangle2D rect, double x, double y) { 1784 // TODO: For JFreeChart 1.2.0, this method should go in the 1785 // ShapeUtilities class 1786 return (x >= rect.getMinX() && x <= rect.getMaxX() 1787 && y >= rect.getMinY() && y <= rect.getMaxY()); 1788 } 1789 1790 }