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