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