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