001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, 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-present, 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; 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 = 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} 667 * or empty). 668 */ 669 protected Range findRangeBounds(CategoryDataset dataset, 670 boolean includeInterval) { 671 if (dataset == null) { 672 return null; 673 } 674 if (getDataBoundsIncludesVisibleSeriesOnly()) { 675 List<Comparable> visibleSeriesKeys = new ArrayList<>(); 676 int seriesCount = dataset.getRowCount(); 677 for (int s = 0; s < seriesCount; s++) { 678 if (isSeriesVisible(s)) { 679 visibleSeriesKeys.add(dataset.getRowKey(s)); 680 } 681 } 682 return DatasetUtils.findRangeBounds(dataset, 683 visibleSeriesKeys, includeInterval); 684 } 685 else { 686 return DatasetUtils.findRangeBounds(dataset, includeInterval); 687 } 688 } 689 690 /** 691 * Returns the Java2D coordinate for the middle of the specified data item. 692 * 693 * @param rowKey the row key. 694 * @param columnKey the column key. 695 * @param dataset the dataset. 696 * @param axis the axis. 697 * @param area the data area. 698 * @param edge the edge along which the axis lies. 699 * 700 * @return The Java2D coordinate for the middle of the item. 701 */ 702 @Override 703 public double getItemMiddle(Comparable rowKey, Comparable columnKey, 704 CategoryDataset dataset, CategoryAxis axis, Rectangle2D area, 705 RectangleEdge edge) { 706 return axis.getCategoryMiddle(columnKey, dataset.getColumnKeys(), area, 707 edge); 708 } 709 710 /** 711 * Draws a background for the data area. The default implementation just 712 * gets the plot to draw the background, but some renderers will override 713 * this behaviour. 714 * 715 * @param g2 the graphics device. 716 * @param plot the plot. 717 * @param dataArea the data area. 718 */ 719 @Override 720 public void drawBackground(Graphics2D g2, CategoryPlot plot, 721 Rectangle2D dataArea) { 722 plot.drawBackground(g2, dataArea); 723 } 724 725 /** 726 * Draws an outline for the data area. The default implementation just 727 * gets the plot to draw the outline, but some renderers will override this 728 * behaviour. 729 * 730 * @param g2 the graphics device. 731 * @param plot the plot. 732 * @param dataArea the data area. 733 */ 734 @Override 735 public void drawOutline(Graphics2D g2, CategoryPlot plot, 736 Rectangle2D dataArea) { 737 plot.drawOutline(g2, dataArea); 738 } 739 740 /** 741 * Draws a grid line against the domain axis. 742 * <P> 743 * Note that this default implementation assumes that the horizontal axis 744 * is the domain axis. If this is not the case, you will need to override 745 * this method. 746 * 747 * @param g2 the graphics device. 748 * @param plot the plot. 749 * @param dataArea the area for plotting data. 750 * @param value the Java2D value at which the grid line should be drawn. 751 * 752 */ 753 @Override 754 public void drawDomainGridline(Graphics2D g2, CategoryPlot plot, 755 Rectangle2D dataArea, double value) { 756 757 Line2D line = null; 758 PlotOrientation orientation = plot.getOrientation(); 759 760 if (orientation == PlotOrientation.HORIZONTAL) { 761 line = new Line2D.Double(dataArea.getMinX(), value, 762 dataArea.getMaxX(), value); 763 } 764 else if (orientation == PlotOrientation.VERTICAL) { 765 line = new Line2D.Double(value, dataArea.getMinY(), value, 766 dataArea.getMaxY()); 767 } 768 769 Paint paint = plot.getDomainGridlinePaint(); 770 if (paint == null) { 771 paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT; 772 } 773 g2.setPaint(paint); 774 775 Stroke stroke = plot.getDomainGridlineStroke(); 776 if (stroke == null) { 777 stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE; 778 } 779 g2.setStroke(stroke); 780 Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); 781 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 782 RenderingHints.VALUE_STROKE_NORMALIZE); 783 g2.draw(line); 784 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); 785 } 786 787 /** 788 * Draws a line perpendicular to the range axis. 789 * 790 * @param g2 the graphics device. 791 * @param plot the plot. 792 * @param axis the value axis. 793 * @param dataArea the area for plotting data. 794 * @param value the value at which the grid line should be drawn. 795 * @param paint the paint ({@code null} not permitted). 796 * @param stroke the stroke ({@code null} not permitted). 797 */ 798 @Override 799 public void drawRangeLine(Graphics2D g2, CategoryPlot plot, ValueAxis axis, 800 Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { 801 802 Range range = axis.getRange(); 803 if (!range.contains(value)) { 804 return; 805 } 806 807 PlotOrientation orientation = plot.getOrientation(); 808 Line2D line = null; 809 double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge()); 810 if (orientation == PlotOrientation.HORIZONTAL) { 811 line = new Line2D.Double(v, dataArea.getMinY(), v, 812 dataArea.getMaxY()); 813 } else if (orientation == PlotOrientation.VERTICAL) { 814 line = new Line2D.Double(dataArea.getMinX(), v, 815 dataArea.getMaxX(), v); 816 } 817 818 g2.setPaint(paint); 819 g2.setStroke(stroke); 820 Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); 821 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 822 RenderingHints.VALUE_STROKE_NORMALIZE); 823 g2.draw(line); 824 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); 825 } 826 827 /** 828 * Draws a marker for the domain axis. 829 * 830 * @param g2 the graphics device (not {@code null}). 831 * @param plot the plot (not {@code null}). 832 * @param axis the range axis (not {@code null}). 833 * @param marker the marker to be drawn (not {@code null}). 834 * @param dataArea the area inside the axes (not {@code null}). 835 * 836 * @see #drawRangeMarker(Graphics2D, CategoryPlot, ValueAxis, Marker, 837 * Rectangle2D) 838 */ 839 @Override 840 public void drawDomainMarker(Graphics2D g2, CategoryPlot plot, 841 CategoryAxis axis, CategoryMarker marker, Rectangle2D dataArea) { 842 843 Comparable category = marker.getKey(); 844 CategoryDataset dataset = plot.getDataset(plot.getIndexOf(this)); 845 int columnIndex = dataset.getColumnIndex(category); 846 if (columnIndex < 0) { 847 return; 848 } 849 850 final Composite savedComposite = g2.getComposite(); 851 g2.setComposite(AlphaComposite.getInstance( 852 AlphaComposite.SRC_OVER, marker.getAlpha())); 853 854 PlotOrientation orientation = plot.getOrientation(); 855 Rectangle2D bounds; 856 if (marker.getDrawAsLine()) { 857 double v = axis.getCategoryMiddle(columnIndex, 858 dataset.getColumnCount(), dataArea, 859 plot.getDomainAxisEdge()); 860 Line2D line = null; 861 if (orientation == PlotOrientation.HORIZONTAL) { 862 line = new Line2D.Double(dataArea.getMinX(), v, 863 dataArea.getMaxX(), v); 864 } 865 else if (orientation == PlotOrientation.VERTICAL) { 866 line = new Line2D.Double(v, dataArea.getMinY(), v, 867 dataArea.getMaxY()); 868 } else { 869 throw new IllegalStateException(); 870 } 871 g2.setPaint(marker.getPaint()); 872 g2.setStroke(marker.getStroke()); 873 g2.draw(line); 874 bounds = line.getBounds2D(); 875 } 876 else { 877 double v0 = axis.getCategoryStart(columnIndex, 878 dataset.getColumnCount(), dataArea, 879 plot.getDomainAxisEdge()); 880 double v1 = axis.getCategoryEnd(columnIndex, 881 dataset.getColumnCount(), dataArea, 882 plot.getDomainAxisEdge()); 883 Rectangle2D area = null; 884 if (orientation == PlotOrientation.HORIZONTAL) { 885 area = new Rectangle2D.Double(dataArea.getMinX(), v0, 886 dataArea.getWidth(), (v1 - v0)); 887 } 888 else if (orientation == PlotOrientation.VERTICAL) { 889 area = new Rectangle2D.Double(v0, dataArea.getMinY(), 890 (v1 - v0), dataArea.getHeight()); 891 } 892 g2.setPaint(marker.getPaint()); 893 g2.fill(area); 894 bounds = area; 895 } 896 897 String label = marker.getLabel(); 898 RectangleAnchor anchor = marker.getLabelAnchor(); 899 if (label != null) { 900 Font labelFont = marker.getLabelFont(); 901 g2.setFont(labelFont); 902 g2.setPaint(marker.getLabelPaint()); 903 Point2D coordinates = calculateDomainMarkerTextAnchorPoint( 904 g2, orientation, dataArea, bounds, marker.getLabelOffset(), 905 marker.getLabelOffsetType(), anchor); 906 TextUtils.drawAlignedString(label, g2, 907 (float) coordinates.getX(), (float) coordinates.getY(), 908 marker.getLabelTextAnchor()); 909 } 910 g2.setComposite(savedComposite); 911 } 912 913 /** 914 * Draws a marker for the range axis. 915 * 916 * @param g2 the graphics device (not {@code null}). 917 * @param plot the plot (not {@code null}). 918 * @param axis the range axis (not {@code null}). 919 * @param marker the marker to be drawn (not {@code null}). 920 * @param dataArea the area inside the axes (not {@code null}). 921 * 922 * @see #drawDomainMarker(Graphics2D, CategoryPlot, CategoryAxis, 923 * CategoryMarker, Rectangle2D) 924 */ 925 @Override 926 public void drawRangeMarker(Graphics2D g2, CategoryPlot plot, 927 ValueAxis axis, Marker marker, Rectangle2D dataArea) { 928 929 if (marker instanceof ValueMarker) { 930 ValueMarker vm = (ValueMarker) marker; 931 double value = vm.getValue(); 932 Range range = axis.getRange(); 933 934 if (!range.contains(value)) { 935 return; 936 } 937 938 final Composite savedComposite = g2.getComposite(); 939 g2.setComposite(AlphaComposite.getInstance( 940 AlphaComposite.SRC_OVER, marker.getAlpha())); 941 942 PlotOrientation orientation = plot.getOrientation(); 943 double v = axis.valueToJava2D(value, dataArea, 944 plot.getRangeAxisEdge()); 945 Line2D line = null; 946 if (orientation == PlotOrientation.HORIZONTAL) { 947 line = new Line2D.Double(v, dataArea.getMinY(), v, 948 dataArea.getMaxY()); 949 } 950 else if (orientation == PlotOrientation.VERTICAL) { 951 line = new Line2D.Double(dataArea.getMinX(), v, 952 dataArea.getMaxX(), v); 953 } else { 954 throw new IllegalStateException(); 955 } 956 957 g2.setPaint(marker.getPaint()); 958 g2.setStroke(marker.getStroke()); 959 g2.draw(line); 960 961 String label = marker.getLabel(); 962 RectangleAnchor anchor = marker.getLabelAnchor(); 963 if (label != null) { 964 Font labelFont = marker.getLabelFont(); 965 g2.setFont(labelFont); 966 Point2D coordinates = calculateRangeMarkerTextAnchorPoint( 967 g2, orientation, dataArea, line.getBounds2D(), 968 marker.getLabelOffset(), LengthAdjustmentType.EXPAND, 969 anchor); 970 Rectangle2D rect = TextUtils.calcAlignedStringBounds(label, g2, 971 (float) coordinates.getX(), (float) coordinates.getY(), 972 marker.getLabelTextAnchor()); 973 g2.setPaint(marker.getLabelBackgroundColor()); 974 g2.fill(rect); 975 g2.setPaint(marker.getLabelPaint()); 976 TextUtils.drawAlignedString(label, g2, 977 (float) coordinates.getX(), (float) coordinates.getY(), 978 marker.getLabelTextAnchor()); 979 } 980 g2.setComposite(savedComposite); 981 } 982 else if (marker instanceof IntervalMarker) { 983 IntervalMarker im = (IntervalMarker) marker; 984 double start = im.getStartValue(); 985 double end = im.getEndValue(); 986 Range range = axis.getRange(); 987 if (!(range.intersects(start, end))) { 988 return; 989 } 990 991 final Composite savedComposite = g2.getComposite(); 992 g2.setComposite(AlphaComposite.getInstance( 993 AlphaComposite.SRC_OVER, marker.getAlpha())); 994 995 double start2d = axis.valueToJava2D(start, dataArea, 996 plot.getRangeAxisEdge()); 997 double end2d = axis.valueToJava2D(end, dataArea, 998 plot.getRangeAxisEdge()); 999 double low = Math.min(start2d, end2d); 1000 double high = Math.max(start2d, end2d); 1001 1002 PlotOrientation orientation = plot.getOrientation(); 1003 Rectangle2D rect = null; 1004 if (orientation == PlotOrientation.HORIZONTAL) { 1005 // clip left and right bounds to data area 1006 low = Math.max(low, dataArea.getMinX()); 1007 high = Math.min(high, dataArea.getMaxX()); 1008 rect = new Rectangle2D.Double(low, 1009 dataArea.getMinY(), high - low, 1010 dataArea.getHeight()); 1011 } 1012 else if (orientation == PlotOrientation.VERTICAL) { 1013 // clip top and bottom bounds to data area 1014 low = Math.max(low, dataArea.getMinY()); 1015 high = Math.min(high, dataArea.getMaxY()); 1016 rect = new Rectangle2D.Double(dataArea.getMinX(), 1017 low, dataArea.getWidth(), 1018 high - low); 1019 } 1020 Paint p = marker.getPaint(); 1021 if (p instanceof GradientPaint) { 1022 GradientPaint gp = (GradientPaint) p; 1023 GradientPaintTransformer t = im.getGradientPaintTransformer(); 1024 if (t != null) { 1025 gp = t.transform(gp, rect); 1026 } 1027 g2.setPaint(gp); 1028 } 1029 else { 1030 g2.setPaint(p); 1031 } 1032 g2.fill(rect); 1033 1034 // now draw the outlines, if visible... 1035 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) { 1036 if (orientation == PlotOrientation.VERTICAL) { 1037 Line2D line = new Line2D.Double(); 1038 double x0 = dataArea.getMinX(); 1039 double x1 = dataArea.getMaxX(); 1040 g2.setPaint(im.getOutlinePaint()); 1041 g2.setStroke(im.getOutlineStroke()); 1042 if (range.contains(start)) { 1043 line.setLine(x0, start2d, x1, start2d); 1044 g2.draw(line); 1045 } 1046 if (range.contains(end)) { 1047 line.setLine(x0, end2d, x1, end2d); 1048 g2.draw(line); 1049 } 1050 } else { // PlotOrientation.HORIZONTAL 1051 Line2D line = new Line2D.Double(); 1052 double y0 = dataArea.getMinY(); 1053 double y1 = dataArea.getMaxY(); 1054 g2.setPaint(im.getOutlinePaint()); 1055 g2.setStroke(im.getOutlineStroke()); 1056 if (range.contains(start)) { 1057 line.setLine(start2d, y0, start2d, y1); 1058 g2.draw(line); 1059 } 1060 if (range.contains(end)) { 1061 line.setLine(end2d, y0, end2d, y1); 1062 g2.draw(line); 1063 } 1064 } 1065 } 1066 1067 String label = marker.getLabel(); 1068 RectangleAnchor anchor = marker.getLabelAnchor(); 1069 if (label != null) { 1070 Font labelFont = marker.getLabelFont(); 1071 g2.setFont(labelFont); 1072 Point2D coords = calculateRangeMarkerTextAnchorPoint( 1073 g2, orientation, dataArea, rect, 1074 marker.getLabelOffset(), marker.getLabelOffsetType(), 1075 anchor); 1076 Rectangle2D r = TextUtils.calcAlignedStringBounds(label, 1077 g2, (float) coords.getX(), (float) coords.getY(), 1078 marker.getLabelTextAnchor()); 1079 g2.setPaint(marker.getLabelBackgroundColor()); 1080 g2.fill(r); 1081 g2.setPaint(marker.getLabelPaint()); 1082 TextUtils.drawAlignedString(label, g2, 1083 (float) coords.getX(), (float) coords.getY(), 1084 marker.getLabelTextAnchor()); 1085 } 1086 g2.setComposite(savedComposite); 1087 } 1088 } 1089 1090 /** 1091 * Calculates the {@code (x, y)} coordinates for drawing the label for a 1092 * marker on the range axis. 1093 * 1094 * @param g2 the graphics device. 1095 * @param orientation the plot orientation. 1096 * @param dataArea the data area. 1097 * @param markerArea the rectangle surrounding the marker. 1098 * @param markerOffset the marker offset. 1099 * @param labelOffsetType the label offset type. 1100 * @param anchor the label anchor. 1101 * 1102 * @return The coordinates for drawing the marker label. 1103 */ 1104 protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2, 1105 PlotOrientation orientation, Rectangle2D dataArea, 1106 Rectangle2D markerArea, RectangleInsets markerOffset, 1107 LengthAdjustmentType labelOffsetType, RectangleAnchor anchor) { 1108 1109 Rectangle2D anchorRect = null; 1110 if (orientation == PlotOrientation.HORIZONTAL) { 1111 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1112 LengthAdjustmentType.CONTRACT, labelOffsetType); 1113 } else if (orientation == PlotOrientation.VERTICAL) { 1114 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1115 labelOffsetType, LengthAdjustmentType.CONTRACT); 1116 } 1117 return anchor.getAnchorPoint(anchorRect); 1118 } 1119 1120 /** 1121 * Calculates the (x, y) coordinates for drawing a marker label. 1122 * 1123 * @param g2 the graphics device. 1124 * @param orientation the plot orientation. 1125 * @param dataArea the data area. 1126 * @param markerArea the rectangle surrounding the marker. 1127 * @param markerOffset the marker offset. 1128 * @param labelOffsetType the label offset type. 1129 * @param anchor the label anchor. 1130 * 1131 * @return The coordinates for drawing the marker label. 1132 */ 1133 protected Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2, 1134 PlotOrientation orientation, Rectangle2D dataArea, 1135 Rectangle2D markerArea, RectangleInsets markerOffset, 1136 LengthAdjustmentType labelOffsetType, RectangleAnchor anchor) { 1137 1138 Rectangle2D anchorRect = null; 1139 if (orientation == PlotOrientation.HORIZONTAL) { 1140 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1141 labelOffsetType, LengthAdjustmentType.CONTRACT); 1142 } else if (orientation == PlotOrientation.VERTICAL) { 1143 anchorRect = markerOffset.createAdjustedRectangle(markerArea, 1144 LengthAdjustmentType.CONTRACT, labelOffsetType); 1145 } 1146 return anchor.getAnchorPoint(anchorRect); 1147 1148 } 1149 1150 /** 1151 * Returns a legend item for a series. This default implementation will 1152 * return {@code null} if {@link #isSeriesVisible(int)} or 1153 * {@link #isSeriesVisibleInLegend(int)} returns {@code false}. 1154 * 1155 * @param datasetIndex the dataset index (zero-based). 1156 * @param series the series index (zero-based). 1157 * 1158 * @return The legend item (possibly {@code null}). 1159 * 1160 * @see #getLegendItems() 1161 */ 1162 @Override 1163 public LegendItem getLegendItem(int datasetIndex, int series) { 1164 1165 CategoryPlot p = getPlot(); 1166 if (p == null) { 1167 return null; 1168 } 1169 1170 // check that a legend item needs to be displayed... 1171 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 1172 return null; 1173 } 1174 1175 CategoryDataset dataset = p.getDataset(datasetIndex); 1176 String label = this.legendItemLabelGenerator.generateLabel(dataset, 1177 series); 1178 String description = label; 1179 String toolTipText = null; 1180 if (this.legendItemToolTipGenerator != null) { 1181 toolTipText = this.legendItemToolTipGenerator.generateLabel( 1182 dataset, series); 1183 } 1184 String urlText = null; 1185 if (this.legendItemURLGenerator != null) { 1186 urlText = this.legendItemURLGenerator.generateLabel(dataset, 1187 series); 1188 } 1189 Shape shape = lookupLegendShape(series); 1190 Paint paint = lookupSeriesPaint(series); 1191 Paint outlinePaint = lookupSeriesOutlinePaint(series); 1192 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 1193 1194 LegendItem item = new LegendItem(label, description, toolTipText, 1195 urlText, shape, paint, outlineStroke, outlinePaint); 1196 item.setLabelFont(lookupLegendTextFont(series)); 1197 Paint labelPaint = lookupLegendTextPaint(series); 1198 if (labelPaint != null) { 1199 item.setLabelPaint(labelPaint); 1200 } 1201 item.setSeriesKey(dataset.getRowKey(series)); 1202 item.setSeriesIndex(series); 1203 item.setDataset(dataset); 1204 item.setDatasetIndex(datasetIndex); 1205 return item; 1206 } 1207 1208 /** 1209 * Tests this renderer for equality with another object. 1210 * 1211 * @param obj the object. 1212 * 1213 * @return {@code true} or {@code false}. 1214 */ 1215 @Override 1216 public boolean equals(Object obj) { 1217 if (obj == this) { 1218 return true; 1219 } 1220 if (!(obj instanceof AbstractCategoryItemRenderer)) { 1221 return false; 1222 } 1223 AbstractCategoryItemRenderer that = (AbstractCategoryItemRenderer) obj; 1224 1225 if (!Objects.equals(this.itemLabelGeneratorMap, 1226 that.itemLabelGeneratorMap)) { 1227 return false; 1228 } 1229 if (!Objects.equals(this.defaultItemLabelGenerator, 1230 that.defaultItemLabelGenerator)) { 1231 return false; 1232 } 1233 if (!Objects.equals(this.toolTipGeneratorMap, 1234 that.toolTipGeneratorMap)) { 1235 return false; 1236 } 1237 if (!Objects.equals(this.defaultToolTipGenerator, 1238 that.defaultToolTipGenerator)) { 1239 return false; 1240 } 1241 if (!Objects.equals(this.itemURLGeneratorMap, 1242 that.itemURLGeneratorMap)) { 1243 return false; 1244 } 1245 if (!Objects.equals(this.defaultItemURLGenerator, 1246 that.defaultItemURLGenerator)) { 1247 return false; 1248 } 1249 if (!Objects.equals(this.legendItemLabelGenerator, 1250 that.legendItemLabelGenerator)) { 1251 return false; 1252 } 1253 if (!Objects.equals(this.legendItemToolTipGenerator, 1254 that.legendItemToolTipGenerator)) { 1255 return false; 1256 } 1257 if (!Objects.equals(this.legendItemURLGenerator, 1258 that.legendItemURLGenerator)) { 1259 return false; 1260 } 1261 return super.equals(obj); 1262 } 1263 1264 /** 1265 * Returns a hash code for the renderer. 1266 * 1267 * @return The hash code. 1268 */ 1269 @Override 1270 public int hashCode() { 1271 int result = super.hashCode(); 1272 return result; 1273 } 1274 1275 /** 1276 * Returns the drawing supplier from the plot. 1277 * 1278 * @return The drawing supplier (possibly {@code null}). 1279 */ 1280 @Override 1281 public DrawingSupplier getDrawingSupplier() { 1282 DrawingSupplier result = null; 1283 CategoryPlot cp = getPlot(); 1284 if (cp != null) { 1285 result = cp.getDrawingSupplier(); 1286 } 1287 return result; 1288 } 1289 1290 /** 1291 * Considers the current (x, y) coordinate and updates the crosshair point 1292 * if it meets the criteria (usually means the (x, y) coordinate is the 1293 * closest to the anchor point so far). 1294 * 1295 * @param crosshairState the crosshair state ({@code null} permitted, 1296 * but the method does nothing in that case). 1297 * @param rowKey the row key. 1298 * @param columnKey the column key. 1299 * @param value the data value. 1300 * @param datasetIndex the dataset index. 1301 * @param transX the x-value translated to Java2D space. 1302 * @param transY the y-value translated to Java2D space. 1303 * @param orientation the plot orientation ({@code null} not permitted). 1304 */ 1305 protected void updateCrosshairValues(CategoryCrosshairState crosshairState, 1306 Comparable rowKey, Comparable columnKey, double value, 1307 int datasetIndex, 1308 double transX, double transY, PlotOrientation orientation) { 1309 1310 Args.nullNotPermitted(orientation, "orientation"); 1311 1312 if (crosshairState != null) { 1313 if (this.plot.isRangeCrosshairLockedOnData()) { 1314 // both axes 1315 crosshairState.updateCrosshairPoint(rowKey, columnKey, value, 1316 datasetIndex, transX, transY, orientation); 1317 } 1318 else { 1319 crosshairState.updateCrosshairX(rowKey, columnKey, 1320 datasetIndex, transX, orientation); 1321 } 1322 } 1323 } 1324 1325 /** 1326 * Draws an item label. 1327 * 1328 * @param g2 the graphics device. 1329 * @param orientation the orientation. 1330 * @param dataset the dataset. 1331 * @param row the row. 1332 * @param column the column. 1333 * @param x the x coordinate (in Java2D space). 1334 * @param y the y coordinate (in Java2D space). 1335 * @param negative indicates a negative value (which affects the item 1336 * label position). 1337 */ 1338 protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation, 1339 CategoryDataset dataset, int row, int column, 1340 double x, double y, boolean negative) { 1341 1342 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 1343 column); 1344 if (generator != null) { 1345 Font labelFont = getItemLabelFont(row, column); 1346 Paint paint = getItemLabelPaint(row, column); 1347 g2.setFont(labelFont); 1348 g2.setPaint(paint); 1349 String label = generator.generateLabel(dataset, row, column); 1350 ItemLabelPosition position; 1351 if (!negative) { 1352 position = getPositiveItemLabelPosition(row, column); 1353 } 1354 else { 1355 position = getNegativeItemLabelPosition(row, column); 1356 } 1357 Point2D anchorPoint = calculateLabelAnchorPoint( 1358 position.getItemLabelAnchor(), x, y, orientation); 1359 TextUtils.drawRotatedString(label, g2, 1360 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 1361 position.getTextAnchor(), 1362 position.getAngle(), position.getRotationAnchor()); 1363 } 1364 1365 } 1366 1367 /** 1368 * Returns an independent copy of the renderer. The {@code plot} 1369 * reference is shallow copied. 1370 * 1371 * @return A clone. 1372 * 1373 * @throws CloneNotSupportedException can be thrown if one of the objects 1374 * belonging to the renderer does not support cloning (for example, 1375 * an item label generator). 1376 */ 1377 @Override 1378 public Object clone() throws CloneNotSupportedException { 1379 AbstractCategoryItemRenderer clone 1380 = (AbstractCategoryItemRenderer) super.clone(); 1381 1382 if (this.itemLabelGeneratorMap != null) { 1383 clone.itemLabelGeneratorMap = CloneUtils.cloneMapValues( 1384 this.itemLabelGeneratorMap); 1385 } 1386 1387 if (this.defaultItemLabelGenerator != null) { 1388 if (this.defaultItemLabelGenerator instanceof PublicCloneable) { 1389 PublicCloneable pc 1390 = (PublicCloneable) this.defaultItemLabelGenerator; 1391 clone.defaultItemLabelGenerator 1392 = (CategoryItemLabelGenerator) pc.clone(); 1393 } 1394 else { 1395 throw new CloneNotSupportedException( 1396 "ItemLabelGenerator not cloneable."); 1397 } 1398 } 1399 1400 if (this.toolTipGeneratorMap != null) { 1401 clone.toolTipGeneratorMap = CloneUtils.cloneMapValues( 1402 this.toolTipGeneratorMap); 1403 } 1404 1405 if (this.defaultToolTipGenerator != null) { 1406 if (this.defaultToolTipGenerator instanceof PublicCloneable) { 1407 PublicCloneable pc 1408 = (PublicCloneable) this.defaultToolTipGenerator; 1409 clone.defaultToolTipGenerator 1410 = (CategoryToolTipGenerator) pc.clone(); 1411 } 1412 else { 1413 throw new CloneNotSupportedException( 1414 "Default tool tip generator not cloneable."); 1415 } 1416 } 1417 1418 if (this.itemURLGeneratorMap != null) { 1419 clone.itemURLGeneratorMap = CloneUtils.cloneMapValues( 1420 this.itemURLGeneratorMap); 1421 } 1422 1423 if (this.defaultItemURLGenerator != null) { 1424 if (this.defaultItemURLGenerator instanceof PublicCloneable) { 1425 PublicCloneable pc 1426 = (PublicCloneable) this.defaultItemURLGenerator; 1427 clone.defaultItemURLGenerator = (CategoryURLGenerator) pc.clone(); 1428 } 1429 else { 1430 throw new CloneNotSupportedException( 1431 "Default item URL generator not cloneable."); 1432 } 1433 } 1434 1435 if (this.legendItemLabelGenerator instanceof PublicCloneable) { 1436 clone.legendItemLabelGenerator = (CategorySeriesLabelGenerator) 1437 ObjectUtils.clone(this.legendItemLabelGenerator); 1438 } 1439 if (this.legendItemToolTipGenerator instanceof PublicCloneable) { 1440 clone.legendItemToolTipGenerator = (CategorySeriesLabelGenerator) 1441 ObjectUtils.clone(this.legendItemToolTipGenerator); 1442 } 1443 if (this.legendItemURLGenerator instanceof PublicCloneable) { 1444 clone.legendItemURLGenerator = (CategorySeriesLabelGenerator) 1445 ObjectUtils.clone(this.legendItemURLGenerator); 1446 } 1447 return clone; 1448 } 1449 1450 /** 1451 * Returns a domain axis for a plot. 1452 * 1453 * @param plot the plot. 1454 * @param index the axis index. 1455 * 1456 * @return A domain axis. 1457 */ 1458 protected CategoryAxis getDomainAxis(CategoryPlot plot, int index) { 1459 CategoryAxis result = plot.getDomainAxis(index); 1460 if (result == null) { 1461 result = plot.getDomainAxis(); 1462 } 1463 return result; 1464 } 1465 1466 /** 1467 * Returns a range axis for a plot. 1468 * 1469 * @param plot the plot. 1470 * @param index the axis index. 1471 * 1472 * @return A range axis. 1473 */ 1474 protected ValueAxis getRangeAxis(CategoryPlot plot, int index) { 1475 ValueAxis result = plot.getRangeAxis(index); 1476 if (result == null) { 1477 result = plot.getRangeAxis(); 1478 } 1479 return result; 1480 } 1481 1482 /** 1483 * Returns a (possibly empty) collection of legend items for the series 1484 * that this renderer is responsible for drawing. 1485 * 1486 * @return The legend item collection (never {@code null}). 1487 * 1488 * @see #getLegendItem(int, int) 1489 */ 1490 @Override 1491 public LegendItemCollection getLegendItems() { 1492 LegendItemCollection result = new LegendItemCollection(); 1493 if (this.plot == null) { 1494 return result; 1495 } 1496 int index = this.plot.getIndexOf(this); 1497 CategoryDataset dataset = this.plot.getDataset(index); 1498 if (dataset == null) { 1499 return result; 1500 } 1501 int seriesCount = dataset.getRowCount(); 1502 if (plot.getRowRenderingOrder().equals(SortOrder.ASCENDING)) { 1503 for (int i = 0; i < seriesCount; i++) { 1504 if (isSeriesVisibleInLegend(i)) { 1505 LegendItem item = getLegendItem(index, i); 1506 if (item != null) { 1507 result.add(item); 1508 } 1509 } 1510 } 1511 } 1512 else { 1513 for (int i = seriesCount - 1; i >= 0; i--) { 1514 if (isSeriesVisibleInLegend(i)) { 1515 LegendItem item = getLegendItem(index, i); 1516 if (item != null) { 1517 result.add(item); 1518 } 1519 } 1520 } 1521 } 1522 return result; 1523 } 1524 1525 /** 1526 * Returns the legend item label generator. 1527 * 1528 * @return The label generator (never {@code null}). 1529 * 1530 * @see #setLegendItemLabelGenerator(CategorySeriesLabelGenerator) 1531 */ 1532 public CategorySeriesLabelGenerator getLegendItemLabelGenerator() { 1533 return this.legendItemLabelGenerator; 1534 } 1535 1536 /** 1537 * Sets the legend item label generator and sends a 1538 * {@link RendererChangeEvent} to all registered listeners. 1539 * 1540 * @param generator the generator ({@code null} not permitted). 1541 * 1542 * @see #getLegendItemLabelGenerator() 1543 */ 1544 public void setLegendItemLabelGenerator( 1545 CategorySeriesLabelGenerator generator) { 1546 Args.nullNotPermitted(generator, "generator"); 1547 this.legendItemLabelGenerator = generator; 1548 fireChangeEvent(); 1549 } 1550 1551 /** 1552 * Returns the legend item tool tip generator. 1553 * 1554 * @return The tool tip generator (possibly {@code null}). 1555 * 1556 * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator) 1557 */ 1558 public CategorySeriesLabelGenerator getLegendItemToolTipGenerator() { 1559 return this.legendItemToolTipGenerator; 1560 } 1561 1562 /** 1563 * Sets the legend item tool tip generator and sends a 1564 * {@link RendererChangeEvent} to all registered listeners. 1565 * 1566 * @param generator the generator ({@code null} permitted). 1567 * 1568 * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator) 1569 */ 1570 public void setLegendItemToolTipGenerator( 1571 CategorySeriesLabelGenerator generator) { 1572 this.legendItemToolTipGenerator = generator; 1573 fireChangeEvent(); 1574 } 1575 1576 /** 1577 * Returns the legend item URL generator. 1578 * 1579 * @return The URL generator (possibly {@code null}). 1580 * 1581 * @see #setLegendItemURLGenerator(CategorySeriesLabelGenerator) 1582 */ 1583 public CategorySeriesLabelGenerator getLegendItemURLGenerator() { 1584 return this.legendItemURLGenerator; 1585 } 1586 1587 /** 1588 * Sets the legend item URL generator and sends a 1589 * {@link RendererChangeEvent} to all registered listeners. 1590 * 1591 * @param generator the generator ({@code null} permitted). 1592 * 1593 * @see #getLegendItemURLGenerator() 1594 */ 1595 public void setLegendItemURLGenerator( 1596 CategorySeriesLabelGenerator generator) { 1597 this.legendItemURLGenerator = generator; 1598 fireChangeEvent(); 1599 } 1600 1601 /** 1602 * Adds an entity with the specified hotspot. 1603 * 1604 * @param entities the entity collection. 1605 * @param dataset the dataset. 1606 * @param row the row index. 1607 * @param column the column index. 1608 * @param hotspot the hotspot ({@code null} not permitted). 1609 */ 1610 protected void addItemEntity(EntityCollection entities, 1611 CategoryDataset dataset, int row, int column, Shape hotspot) { 1612 Args.nullNotPermitted(hotspot, "hotspot"); 1613 if (!getItemCreateEntity(row, column)) { 1614 return; 1615 } 1616 String tip = null; 1617 CategoryToolTipGenerator tipster = getToolTipGenerator(row, column); 1618 if (tipster != null) { 1619 tip = tipster.generateToolTip(dataset, row, column); 1620 } 1621 String url = null; 1622 CategoryURLGenerator urlster = getItemURLGenerator(row, column); 1623 if (urlster != null) { 1624 url = urlster.generateURL(dataset, row, column); 1625 } 1626 CategoryItemEntity entity = new CategoryItemEntity(hotspot, tip, url, 1627 dataset, dataset.getRowKey(row), dataset.getColumnKey(column)); 1628 entities.add(entity); 1629 } 1630 1631 /** 1632 * Adds an entity to the collection. 1633 * 1634 * @param entities the entity collection being populated. 1635 * @param hotspot the entity area (if {@code null} a default will be 1636 * used). 1637 * @param dataset the dataset. 1638 * @param row the series. 1639 * @param column the item. 1640 * @param entityX the entity's center x-coordinate in user space (only 1641 * used if {@code area} is {@code null}). 1642 * @param entityY the entity's center y-coordinate in user space (only 1643 * used if {@code area} is {@code null}). 1644 */ 1645 protected void addEntity(EntityCollection entities, Shape hotspot, 1646 CategoryDataset dataset, int row, int column, 1647 double entityX, double entityY) { 1648 if (!getItemCreateEntity(row, column)) { 1649 return; 1650 } 1651 Shape s = hotspot; 1652 if (hotspot == null) { 1653 double r = getDefaultEntityRadius(); 1654 double w = r * 2; 1655 if (getPlot().getOrientation() == PlotOrientation.VERTICAL) { 1656 s = new Ellipse2D.Double(entityX - r, entityY - r, w, w); 1657 } 1658 else { 1659 s = new Ellipse2D.Double(entityY - r, entityX - r, w, w); 1660 } 1661 } 1662 String tip = null; 1663 CategoryToolTipGenerator generator = getToolTipGenerator(row, column); 1664 if (generator != null) { 1665 tip = generator.generateToolTip(dataset, row, column); 1666 } 1667 String url = null; 1668 CategoryURLGenerator urlster = getItemURLGenerator(row, column); 1669 if (urlster != null) { 1670 url = urlster.generateURL(dataset, row, column); 1671 } 1672 CategoryItemEntity entity = new CategoryItemEntity(s, tip, url, 1673 dataset, dataset.getRowKey(row), dataset.getColumnKey(column)); 1674 entities.add(entity); 1675 } 1676 1677}