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