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