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