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