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