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