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