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