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