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