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