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