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