001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------ 028 * XYBarRenderer.java 029 * ------------------ 030 * (C) Copyright 2001-2007, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Richard Atkinson; 034 * Christian W. Zuckschwerdt; 035 * Bill Kelemen; 036 * 037 * $Id: XYBarRenderer.java,v 1.14.2.12 2007/03/05 15:11:44 mungady Exp $ 038 * 039 * Changes 040 * ------- 041 * 13-Dec-2001 : Version 1, makes VerticalXYBarPlot class redundant (DG); 042 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 043 * 09-Apr-2002 : Removed the translated zero from the drawItem method. Override 044 * the initialise() method to calculate it (DG); 045 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 046 * 25-Jun-2002 : Removed redundant import (DG); 047 * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML 048 * image maps (RA); 049 * 25-Mar-2003 : Implemented Serializable (DG); 050 * 01-May-2003 : Modified drawItem() method signature (DG); 051 * 30-Jul-2003 : Modified entity constructor (CZ); 052 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 053 * 24-Aug-2003 : Added null checks in drawItem (BK); 054 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 055 * 07-Oct-2003 : Added renderer state (DG); 056 * 05-Dec-2003 : Changed call to obtain outline paint (DG); 057 * 10-Feb-2004 : Added state class, updated drawItem() method to make 058 * cut-and-paste overriding easier, and replaced property change 059 * with RendererChangeEvent (DG); 060 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 061 * 26-Apr-2004 : Added gradient paint transformer (DG); 062 * 19-May-2004 : Fixed bug (879709) with bar zero value for secondary axis (DG); 063 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 064 * getYValue() (DG); 065 * 01-Sep-2004 : Added a flag to control whether or not the bar outlines are 066 * drawn (DG); 067 * 03-Sep-2004 : Added option to use y-interval from dataset to determine the 068 * length of the bars (DG); 069 * 08-Sep-2004 : Added equals() method and updated clone() method (DG); 070 * 26-Jan-2005 : Added override for getLegendItem() method (DG); 071 * 20-Apr-2005 : Use generators for label tooltips and URLs (DG); 072 * 19-May-2005 : Added minimal item label implementation - needs improving (DG); 073 * 14-Oct-2005 : Fixed rendering problem with inverted axes (DG); 074 * ------------- JFREECHART 1.0.x --------------------------------------------- 075 * 21-Jun-2006 : Improved item label handling - see bug 1501768 (DG); 076 * 24-Aug-2006 : Added crosshair support (DG); 077 * 13-Dec-2006 : Updated getLegendItems() to return gradient paint 078 * transformer (DG); 079 * 02-Feb-2007 : Changed setUseYInterval() to only notify when the flag 080 * changes (DG); 081 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); 082 * 09-Feb-2007 : Updated getLegendItem() to observe drawBarOutline flag (DG); 083 * 05-Mar-2007 : Applied patch 1671126 by Sergei Ivanov, to fix rendering with 084 * LogarithmicAxis (DG); 085 * 086 */ 087 088 package org.jfree.chart.renderer.xy; 089 090 import java.awt.Font; 091 import java.awt.GradientPaint; 092 import java.awt.Graphics2D; 093 import java.awt.Paint; 094 import java.awt.Shape; 095 import java.awt.Stroke; 096 import java.awt.geom.Point2D; 097 import java.awt.geom.Rectangle2D; 098 import java.io.IOException; 099 import java.io.ObjectInputStream; 100 import java.io.ObjectOutputStream; 101 import java.io.Serializable; 102 103 import org.jfree.chart.LegendItem; 104 import org.jfree.chart.axis.ValueAxis; 105 import org.jfree.chart.entity.EntityCollection; 106 import org.jfree.chart.entity.XYItemEntity; 107 import org.jfree.chart.event.RendererChangeEvent; 108 import org.jfree.chart.labels.ItemLabelAnchor; 109 import org.jfree.chart.labels.ItemLabelPosition; 110 import org.jfree.chart.labels.XYItemLabelGenerator; 111 import org.jfree.chart.labels.XYSeriesLabelGenerator; 112 import org.jfree.chart.labels.XYToolTipGenerator; 113 import org.jfree.chart.plot.CrosshairState; 114 import org.jfree.chart.plot.PlotOrientation; 115 import org.jfree.chart.plot.PlotRenderingInfo; 116 import org.jfree.chart.plot.XYPlot; 117 import org.jfree.data.Range; 118 import org.jfree.data.general.DatasetUtilities; 119 import org.jfree.data.xy.IntervalXYDataset; 120 import org.jfree.data.xy.XYDataset; 121 import org.jfree.io.SerialUtilities; 122 import org.jfree.text.TextUtilities; 123 import org.jfree.ui.GradientPaintTransformer; 124 import org.jfree.ui.RectangleEdge; 125 import org.jfree.ui.StandardGradientPaintTransformer; 126 import org.jfree.util.ObjectUtilities; 127 import org.jfree.util.PublicCloneable; 128 import org.jfree.util.ShapeUtilities; 129 130 /** 131 * A renderer that draws bars on an {@link XYPlot} (requires an 132 * {@link IntervalXYDataset}). 133 */ 134 public class XYBarRenderer extends AbstractXYItemRenderer 135 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 136 137 /** For serialization. */ 138 private static final long serialVersionUID = 770559577251370036L; 139 140 /** 141 * The state class used by this renderer. 142 */ 143 protected class XYBarRendererState extends XYItemRendererState { 144 145 /** Base for bars against the range axis, in Java 2D space. */ 146 private double g2Base; 147 148 /** 149 * Creates a new state object. 150 * 151 * @param info the plot rendering info. 152 */ 153 public XYBarRendererState(PlotRenderingInfo info) { 154 super(info); 155 } 156 157 /** 158 * Returns the base (range) value in Java 2D space. 159 * 160 * @return The base value. 161 */ 162 public double getG2Base() { 163 return this.g2Base; 164 } 165 166 /** 167 * Sets the range axis base in Java2D space. 168 * 169 * @param value the value. 170 */ 171 public void setG2Base(double value) { 172 this.g2Base = value; 173 } 174 } 175 176 /** The default base value for the bars. */ 177 private double base; 178 179 /** 180 * A flag that controls whether the bars use the y-interval supplied by the 181 * dataset. 182 */ 183 private boolean useYInterval; 184 185 /** Percentage margin (to reduce the width of bars). */ 186 private double margin; 187 188 /** A flag that controls whether or not bar outlines are drawn. */ 189 private boolean drawBarOutline; 190 191 /** 192 * An optional class used to transform gradient paint objects to fit each 193 * bar. 194 */ 195 private GradientPaintTransformer gradientPaintTransformer; 196 197 /** 198 * The shape used to represent a bar in each legend item (this should never 199 * be <code>null</code>). 200 */ 201 private transient Shape legendBar; 202 203 /** 204 * The fallback position if a positive item label doesn't fit inside the 205 * bar. 206 */ 207 private ItemLabelPosition positiveItemLabelPositionFallback; 208 209 /** 210 * The fallback position if a negative item label doesn't fit inside the 211 * bar. 212 */ 213 private ItemLabelPosition negativeItemLabelPositionFallback; 214 215 /** 216 * The default constructor. 217 */ 218 public XYBarRenderer() { 219 this(0.0); 220 } 221 222 /** 223 * Constructs a new renderer. 224 * 225 * @param margin the percentage amount to trim from the width of each bar. 226 */ 227 public XYBarRenderer(double margin) { 228 super(); 229 this.margin = margin; 230 this.base = 0.0; 231 this.useYInterval = false; 232 this.gradientPaintTransformer = new StandardGradientPaintTransformer(); 233 this.drawBarOutline = true; 234 this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0); 235 } 236 237 /** 238 * Returns the base value for the bars. 239 * 240 * @return The base value for the bars. 241 * 242 * @see #setBase(double) 243 */ 244 public double getBase() { 245 return this.base; 246 } 247 248 /** 249 * Sets the base value for the bars and sends a {@link RendererChangeEvent} 250 * to all registered listeners. The base value is not used if the dataset's 251 * y-interval is being used to determine the bar length. 252 * 253 * @param base the new base value. 254 * 255 * @see #getBase() 256 * @see #getUseYInterval() 257 */ 258 public void setBase(double base) { 259 this.base = base; 260 notifyListeners(new RendererChangeEvent(this)); 261 } 262 263 /** 264 * Returns a flag that determines whether the y-interval from the dataset is 265 * used to calculate the length of each bar. 266 * 267 * @return A boolean. 268 * 269 * @see #setUseYInterval(boolean) 270 */ 271 public boolean getUseYInterval() { 272 return this.useYInterval; 273 } 274 275 /** 276 * Sets the flag that determines whether the y-interval from the dataset is 277 * used to calculate the length of each bar, and sends a 278 * {@link RendererChangeEvent} to all registered listeners. 279 * 280 * @param use the flag. 281 * 282 * @see #getUseYInterval() 283 */ 284 public void setUseYInterval(boolean use) { 285 if (this.useYInterval != use) { 286 this.useYInterval = use; 287 notifyListeners(new RendererChangeEvent(this)); 288 } 289 } 290 291 /** 292 * Returns the margin which is a percentage amount by which the bars are 293 * trimmed. 294 * 295 * @return The margin. 296 * 297 * @see #setMargin(double) 298 */ 299 public double getMargin() { 300 return this.margin; 301 } 302 303 /** 304 * Sets the percentage amount by which the bars are trimmed and sends a 305 * {@link RendererChangeEvent} to all registered listeners. 306 * 307 * @param margin the new margin. 308 * 309 * @see #getMargin() 310 */ 311 public void setMargin(double margin) { 312 this.margin = margin; 313 notifyListeners(new RendererChangeEvent(this)); 314 } 315 316 /** 317 * Returns a flag that controls whether or not bar outlines are drawn. 318 * 319 * @return A boolean. 320 * 321 * @see #setDrawBarOutline(boolean) 322 */ 323 public boolean isDrawBarOutline() { 324 return this.drawBarOutline; 325 } 326 327 /** 328 * Sets the flag that controls whether or not bar outlines are drawn and 329 * sends a {@link RendererChangeEvent} to all registered listeners. 330 * 331 * @param draw the flag. 332 * 333 * @see #isDrawBarOutline() 334 */ 335 public void setDrawBarOutline(boolean draw) { 336 this.drawBarOutline = draw; 337 notifyListeners(new RendererChangeEvent(this)); 338 } 339 340 /** 341 * Returns the gradient paint transformer (an object used to transform 342 * gradient paint objects to fit each bar. 343 * 344 * @return A transformer (<code>null</code> possible). 345 * 346 * @see #setGradientPaintTransformer(GradientPaintTransformer) 347 */ 348 public GradientPaintTransformer getGradientPaintTransformer() { 349 return this.gradientPaintTransformer; 350 } 351 352 /** 353 * Sets the gradient paint transformer and sends a 354 * {@link RendererChangeEvent} to all registered listeners. 355 * 356 * @param transformer the transformer (<code>null</code> permitted). 357 * 358 * @see #getGradientPaintTransformer() 359 */ 360 public void setGradientPaintTransformer( 361 GradientPaintTransformer transformer) { 362 this.gradientPaintTransformer = transformer; 363 notifyListeners(new RendererChangeEvent(this)); 364 } 365 366 /** 367 * Returns the shape used to represent bars in each legend item. 368 * 369 * @return The shape used to represent bars in each legend item (never 370 * <code>null</code>). 371 * 372 * @see #setLegendBar(Shape) 373 */ 374 public Shape getLegendBar() { 375 return this.legendBar; 376 } 377 378 /** 379 * Sets the shape used to represent bars in each legend item and sends a 380 * {@link RendererChangeEvent} to all registered listeners. 381 * 382 * @param bar the bar shape (<code>null</code> not permitted). 383 * 384 * @see #getLegendBar() 385 */ 386 public void setLegendBar(Shape bar) { 387 if (bar == null) { 388 throw new IllegalArgumentException("Null 'bar' argument."); 389 } 390 this.legendBar = bar; 391 notifyListeners(new RendererChangeEvent(this)); 392 } 393 394 /** 395 * Returns the fallback position for positive item labels that don't fit 396 * within a bar. 397 * 398 * @return The fallback position (<code>null</code> possible). 399 * 400 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition) 401 * @since 1.0.2 402 */ 403 public ItemLabelPosition getPositiveItemLabelPositionFallback() { 404 return this.positiveItemLabelPositionFallback; 405 } 406 407 /** 408 * Sets the fallback position for positive item labels that don't fit 409 * within a bar, and sends a {@link RendererChangeEvent} to all registered 410 * listeners. 411 * 412 * @param position the position (<code>null</code> permitted). 413 * 414 * @see #getPositiveItemLabelPositionFallback() 415 * @since 1.0.2 416 */ 417 public void setPositiveItemLabelPositionFallback( 418 ItemLabelPosition position) { 419 this.positiveItemLabelPositionFallback = position; 420 notifyListeners(new RendererChangeEvent(this)); 421 } 422 423 /** 424 * Returns the fallback position for negative item labels that don't fit 425 * within a bar. 426 * 427 * @return The fallback position (<code>null</code> possible). 428 * 429 * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition) 430 * @since 1.0.2 431 */ 432 public ItemLabelPosition getNegativeItemLabelPositionFallback() { 433 return this.negativeItemLabelPositionFallback; 434 } 435 436 /** 437 * Sets the fallback position for negative item labels that don't fit 438 * within a bar, and sends a {@link RendererChangeEvent} to all registered 439 * listeners. 440 * 441 * @param position the position (<code>null</code> permitted). 442 * 443 * @see #getNegativeItemLabelPositionFallback() 444 * @since 1.0.2 445 */ 446 public void setNegativeItemLabelPositionFallback( 447 ItemLabelPosition position) { 448 this.negativeItemLabelPositionFallback = position; 449 notifyListeners(new RendererChangeEvent(this)); 450 } 451 452 /** 453 * Initialises the renderer and returns a state object that should be 454 * passed to all subsequent calls to the drawItem() method. Here we 455 * calculate the Java2D y-coordinate for zero, since all the bars have 456 * their bases fixed at zero. 457 * 458 * @param g2 the graphics device. 459 * @param dataArea the area inside the axes. 460 * @param plot the plot. 461 * @param dataset the data. 462 * @param info an optional info collection object to return data back to 463 * the caller. 464 * 465 * @return A state object. 466 */ 467 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 468 XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { 469 470 XYBarRendererState state = new XYBarRendererState(info); 471 ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf( 472 dataset)); 473 state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea, 474 plot.getRangeAxisEdge())); 475 return state; 476 477 } 478 479 /** 480 * Returns a default legend item for the specified series. Subclasses 481 * should override this method to generate customised items. 482 * 483 * @param datasetIndex the dataset index (zero-based). 484 * @param series the series index (zero-based). 485 * 486 * @return A legend item for the series. 487 */ 488 public LegendItem getLegendItem(int datasetIndex, int series) { 489 LegendItem result = null; 490 XYPlot xyplot = getPlot(); 491 if (xyplot != null) { 492 XYDataset dataset = xyplot.getDataset(datasetIndex); 493 if (dataset != null) { 494 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); 495 String label = lg.generateLabel(dataset, series); 496 String description = label; 497 String toolTipText = null; 498 if (getLegendItemToolTipGenerator() != null) { 499 toolTipText = getLegendItemToolTipGenerator().generateLabel( 500 dataset, series); 501 } 502 String urlText = null; 503 if (getLegendItemURLGenerator() != null) { 504 urlText = getLegendItemURLGenerator().generateLabel( 505 dataset, series); 506 } 507 Shape shape = this.legendBar; 508 Paint paint = getSeriesPaint(series); 509 Paint outlinePaint = getSeriesOutlinePaint(series); 510 Stroke outlineStroke = getSeriesOutlineStroke(series); 511 if (this.drawBarOutline) { 512 result = new LegendItem(label, description, toolTipText, 513 urlText, shape, paint, outlineStroke, outlinePaint); 514 } 515 else { 516 result = new LegendItem(label, description, toolTipText, 517 urlText, shape, paint); 518 } 519 if (getGradientPaintTransformer() != null) { 520 result.setFillPaintTransformer( 521 getGradientPaintTransformer()); 522 } 523 } 524 } 525 return result; 526 } 527 528 /** 529 * Draws the visual representation of a single data item. 530 * 531 * @param g2 the graphics device. 532 * @param state the renderer state. 533 * @param dataArea the area within which the plot is being drawn. 534 * @param info collects information about the drawing. 535 * @param plot the plot (can be used to obtain standard color 536 * information etc). 537 * @param domainAxis the domain axis. 538 * @param rangeAxis the range axis. 539 * @param dataset the dataset. 540 * @param series the series index (zero-based). 541 * @param item the item index (zero-based). 542 * @param crosshairState crosshair information for the plot 543 * (<code>null</code> permitted). 544 * @param pass the pass index. 545 */ 546 public void drawItem(Graphics2D g2, 547 XYItemRendererState state, 548 Rectangle2D dataArea, 549 PlotRenderingInfo info, 550 XYPlot plot, 551 ValueAxis domainAxis, 552 ValueAxis rangeAxis, 553 XYDataset dataset, 554 int series, 555 int item, 556 CrosshairState crosshairState, 557 int pass) { 558 559 if (!getItemVisible(series, item)) { 560 return; 561 } 562 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 563 564 double value0; 565 double value1; 566 if (this.useYInterval) { 567 value0 = intervalDataset.getStartYValue(series, item); 568 value1 = intervalDataset.getEndYValue(series, item); 569 } 570 else { 571 value0 = this.base; 572 value1 = intervalDataset.getYValue(series, item); 573 } 574 if (Double.isNaN(value0) || Double.isNaN(value1)) { 575 return; 576 } 577 if (value0 <= value1) { 578 if (!rangeAxis.getRange().intersects(value0, value1)) { 579 return; 580 } 581 } 582 else { 583 if (!rangeAxis.getRange().intersects(value1, value0)) { 584 return; 585 } 586 } 587 588 double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea, 589 plot.getRangeAxisEdge()); 590 double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea, 591 plot.getRangeAxisEdge()); 592 double bottom = Math.min(translatedValue0, translatedValue1); 593 double top = Math.max(translatedValue0, translatedValue1); 594 595 double startX = intervalDataset.getStartXValue(series, item); 596 if (Double.isNaN(startX)) { 597 return; 598 } 599 double endX = intervalDataset.getEndXValue(series, item); 600 if (Double.isNaN(endX)) { 601 return; 602 } 603 if (startX <= endX) { 604 if (!domainAxis.getRange().intersects(startX, endX)) { 605 return; 606 } 607 } 608 else { 609 if (!domainAxis.getRange().intersects(endX, startX)) { 610 return; 611 } 612 } 613 614 RectangleEdge location = plot.getDomainAxisEdge(); 615 double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, 616 location); 617 double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, 618 location); 619 620 double translatedWidth = Math.max(1, Math.abs(translatedEndX 621 - translatedStartX)); 622 623 if (getMargin() > 0.0) { 624 double cut = translatedWidth * getMargin(); 625 translatedWidth = translatedWidth - cut; 626 translatedStartX = translatedStartX + cut / 2; 627 } 628 629 Rectangle2D bar = null; 630 PlotOrientation orientation = plot.getOrientation(); 631 if (orientation == PlotOrientation.HORIZONTAL) { 632 // clip left and right bounds to data area 633 bottom = Math.max(bottom, dataArea.getMinX()); 634 top = Math.min(top, dataArea.getMaxX()); 635 bar = new Rectangle2D.Double( 636 bottom, 637 Math.min(translatedStartX, translatedEndX), 638 top - bottom, translatedWidth); 639 } 640 else if (orientation == PlotOrientation.VERTICAL) { 641 // clip top and bottom bounds to data area 642 bottom = Math.max(bottom, dataArea.getMinY()); 643 top = Math.min(top, dataArea.getMaxY()); 644 bar = new Rectangle2D.Double( 645 Math.min(translatedStartX, translatedEndX), 646 bottom, 647 translatedWidth, top - bottom); 648 } 649 650 Paint itemPaint = getItemPaint(series, item); 651 if (getGradientPaintTransformer() 652 != null && itemPaint instanceof GradientPaint) { 653 GradientPaint gp = (GradientPaint) itemPaint; 654 itemPaint = getGradientPaintTransformer().transform(gp, bar); 655 } 656 g2.setPaint(itemPaint); 657 g2.fill(bar); 658 if (isDrawBarOutline() 659 && Math.abs(translatedEndX - translatedStartX) > 3) { 660 Stroke stroke = getItemOutlineStroke(series, item); 661 Paint paint = getItemOutlinePaint(series, item); 662 if (stroke != null && paint != null) { 663 g2.setStroke(stroke); 664 g2.setPaint(paint); 665 g2.draw(bar); 666 } 667 } 668 669 if (isItemLabelVisible(series, item)) { 670 XYItemLabelGenerator generator = getItemLabelGenerator(series, 671 item); 672 drawItemLabel(g2, dataset, series, item, plot, generator, bar, 673 value1 < 0.0); 674 } 675 676 // update the crosshair point 677 double x1 = (startX + endX) / 2.0; 678 double y1 = dataset.getYValue(series, item); 679 double transX1 = domainAxis.valueToJava2D(x1, dataArea, location); 680 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 681 plot.getRangeAxisEdge()); 682 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 683 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 684 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 685 rangeAxisIndex, transX1, transY1, plot.getOrientation()); 686 687 // add an entity for the item... 688 if (info != null) { 689 EntityCollection entities = info.getOwner().getEntityCollection(); 690 if (entities != null) { 691 String tip = null; 692 XYToolTipGenerator generator = getToolTipGenerator(series, 693 item); 694 if (generator != null) { 695 tip = generator.generateToolTip(dataset, series, item); 696 } 697 String url = null; 698 if (getURLGenerator() != null) { 699 url = getURLGenerator().generateURL(dataset, series, item); 700 } 701 XYItemEntity entity = new XYItemEntity(bar, dataset, series, 702 item, tip, url); 703 entities.add(entity); 704 } 705 } 706 707 } 708 709 /** 710 * Draws an item label. This method is overridden so that the bar can be 711 * used to calculate the label anchor point. 712 * 713 * @param g2 the graphics device. 714 * @param dataset the dataset. 715 * @param series the series index. 716 * @param item the item index. 717 * @param plot the plot. 718 * @param generator the label generator. 719 * @param bar the bar. 720 * @param negative a flag indicating a negative value. 721 */ 722 protected void drawItemLabel(Graphics2D g2, XYDataset dataset, 723 int series, int item, XYPlot plot, XYItemLabelGenerator generator, 724 Rectangle2D bar, boolean negative) { 725 726 String label = generator.generateLabel(dataset, series, item); 727 if (label == null) { 728 return; // nothing to do 729 } 730 731 Font labelFont = getItemLabelFont(series, item); 732 g2.setFont(labelFont); 733 Paint paint = getItemLabelPaint(series, item); 734 g2.setPaint(paint); 735 736 // find out where to place the label... 737 ItemLabelPosition position = null; 738 if (!negative) { 739 position = getPositiveItemLabelPosition(series, item); 740 } 741 else { 742 position = getNegativeItemLabelPosition(series, item); 743 } 744 745 // work out the label anchor point... 746 Point2D anchorPoint = calculateLabelAnchorPoint( 747 position.getItemLabelAnchor(), bar, plot.getOrientation()); 748 749 if (isInternalAnchor(position.getItemLabelAnchor())) { 750 Shape bounds = TextUtilities.calculateRotatedStringBounds(label, 751 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), 752 position.getTextAnchor(), position.getAngle(), 753 position.getRotationAnchor()); 754 755 if (bounds != null) { 756 if (!bar.contains(bounds.getBounds2D())) { 757 if (!negative) { 758 position = getPositiveItemLabelPositionFallback(); 759 } 760 else { 761 position = getNegativeItemLabelPositionFallback(); 762 } 763 if (position != null) { 764 anchorPoint = calculateLabelAnchorPoint( 765 position.getItemLabelAnchor(), bar, 766 plot.getOrientation()); 767 } 768 } 769 } 770 771 } 772 773 if (position != null) { 774 TextUtilities.drawRotatedString(label, g2, 775 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 776 position.getTextAnchor(), position.getAngle(), 777 position.getRotationAnchor()); 778 } 779 } 780 781 /** 782 * Calculates the item label anchor point. 783 * 784 * @param anchor the anchor. 785 * @param bar the bar. 786 * @param orientation the plot orientation. 787 * 788 * @return The anchor point. 789 */ 790 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor, 791 Rectangle2D bar, PlotOrientation orientation) { 792 793 Point2D result = null; 794 double offset = getItemLabelAnchorOffset(); 795 double x0 = bar.getX() - offset; 796 double x1 = bar.getX(); 797 double x2 = bar.getX() + offset; 798 double x3 = bar.getCenterX(); 799 double x4 = bar.getMaxX() - offset; 800 double x5 = bar.getMaxX(); 801 double x6 = bar.getMaxX() + offset; 802 803 double y0 = bar.getMaxY() + offset; 804 double y1 = bar.getMaxY(); 805 double y2 = bar.getMaxY() - offset; 806 double y3 = bar.getCenterY(); 807 double y4 = bar.getMinY() + offset; 808 double y5 = bar.getMinY(); 809 double y6 = bar.getMinY() - offset; 810 811 if (anchor == ItemLabelAnchor.CENTER) { 812 result = new Point2D.Double(x3, y3); 813 } 814 else if (anchor == ItemLabelAnchor.INSIDE1) { 815 result = new Point2D.Double(x4, y4); 816 } 817 else if (anchor == ItemLabelAnchor.INSIDE2) { 818 result = new Point2D.Double(x4, y4); 819 } 820 else if (anchor == ItemLabelAnchor.INSIDE3) { 821 result = new Point2D.Double(x4, y3); 822 } 823 else if (anchor == ItemLabelAnchor.INSIDE4) { 824 result = new Point2D.Double(x4, y2); 825 } 826 else if (anchor == ItemLabelAnchor.INSIDE5) { 827 result = new Point2D.Double(x4, y2); 828 } 829 else if (anchor == ItemLabelAnchor.INSIDE6) { 830 result = new Point2D.Double(x3, y2); 831 } 832 else if (anchor == ItemLabelAnchor.INSIDE7) { 833 result = new Point2D.Double(x2, y2); 834 } 835 else if (anchor == ItemLabelAnchor.INSIDE8) { 836 result = new Point2D.Double(x2, y2); 837 } 838 else if (anchor == ItemLabelAnchor.INSIDE9) { 839 result = new Point2D.Double(x2, y3); 840 } 841 else if (anchor == ItemLabelAnchor.INSIDE10) { 842 result = new Point2D.Double(x2, y4); 843 } 844 else if (anchor == ItemLabelAnchor.INSIDE11) { 845 result = new Point2D.Double(x2, y4); 846 } 847 else if (anchor == ItemLabelAnchor.INSIDE12) { 848 result = new Point2D.Double(x3, y4); 849 } 850 else if (anchor == ItemLabelAnchor.OUTSIDE1) { 851 result = new Point2D.Double(x5, y6); 852 } 853 else if (anchor == ItemLabelAnchor.OUTSIDE2) { 854 result = new Point2D.Double(x6, y5); 855 } 856 else if (anchor == ItemLabelAnchor.OUTSIDE3) { 857 result = new Point2D.Double(x6, y3); 858 } 859 else if (anchor == ItemLabelAnchor.OUTSIDE4) { 860 result = new Point2D.Double(x6, y1); 861 } 862 else if (anchor == ItemLabelAnchor.OUTSIDE5) { 863 result = new Point2D.Double(x5, y0); 864 } 865 else if (anchor == ItemLabelAnchor.OUTSIDE6) { 866 result = new Point2D.Double(x3, y0); 867 } 868 else if (anchor == ItemLabelAnchor.OUTSIDE7) { 869 result = new Point2D.Double(x1, y0); 870 } 871 else if (anchor == ItemLabelAnchor.OUTSIDE8) { 872 result = new Point2D.Double(x0, y1); 873 } 874 else if (anchor == ItemLabelAnchor.OUTSIDE9) { 875 result = new Point2D.Double(x0, y3); 876 } 877 else if (anchor == ItemLabelAnchor.OUTSIDE10) { 878 result = new Point2D.Double(x0, y5); 879 } 880 else if (anchor == ItemLabelAnchor.OUTSIDE11) { 881 result = new Point2D.Double(x1, y6); 882 } 883 else if (anchor == ItemLabelAnchor.OUTSIDE12) { 884 result = new Point2D.Double(x3, y6); 885 } 886 887 return result; 888 889 } 890 891 /** 892 * Returns <code>true</code> if the specified anchor point is inside a bar. 893 * 894 * @param anchor the anchor point. 895 * 896 * @return A boolean. 897 */ 898 private boolean isInternalAnchor(ItemLabelAnchor anchor) { 899 return anchor == ItemLabelAnchor.CENTER 900 || anchor == ItemLabelAnchor.INSIDE1 901 || anchor == ItemLabelAnchor.INSIDE2 902 || anchor == ItemLabelAnchor.INSIDE3 903 || anchor == ItemLabelAnchor.INSIDE4 904 || anchor == ItemLabelAnchor.INSIDE5 905 || anchor == ItemLabelAnchor.INSIDE6 906 || anchor == ItemLabelAnchor.INSIDE7 907 || anchor == ItemLabelAnchor.INSIDE8 908 || anchor == ItemLabelAnchor.INSIDE9 909 || anchor == ItemLabelAnchor.INSIDE10 910 || anchor == ItemLabelAnchor.INSIDE11 911 || anchor == ItemLabelAnchor.INSIDE12; 912 } 913 914 /** 915 * Returns the lower and upper bounds (range) of the x-values in the 916 * specified dataset. Since this renderer uses the x-interval in the 917 * dataset, this is taken into account for the range. 918 * 919 * @param dataset the dataset (<code>null</code> permitted). 920 * 921 * @return The range (<code>null</code> if the dataset is 922 * <code>null</code> or empty). 923 */ 924 public Range findDomainBounds(XYDataset dataset) { 925 if (dataset != null) { 926 return DatasetUtilities.findDomainBounds(dataset, true); 927 } 928 else { 929 return null; 930 } 931 } 932 933 /** 934 * Returns a clone of the renderer. 935 * 936 * @return A clone. 937 * 938 * @throws CloneNotSupportedException if the renderer cannot be cloned. 939 */ 940 public Object clone() throws CloneNotSupportedException { 941 XYBarRenderer result = (XYBarRenderer) super.clone(); 942 if (this.gradientPaintTransformer != null) { 943 result.gradientPaintTransformer = (GradientPaintTransformer) 944 ObjectUtilities.clone(this.gradientPaintTransformer); 945 } 946 result.legendBar = ShapeUtilities.clone(this.legendBar); 947 return result; 948 } 949 950 /** 951 * Tests this renderer for equality with an arbitrary object. 952 * 953 * @param obj the object to test against (<code>null</code> permitted). 954 * 955 * @return A boolean. 956 */ 957 public boolean equals(Object obj) { 958 if (obj == this) { 959 return true; 960 } 961 if (!(obj instanceof XYBarRenderer)) { 962 return false; 963 } 964 if (!super.equals(obj)) { 965 return false; 966 } 967 XYBarRenderer that = (XYBarRenderer) obj; 968 if (this.base != that.base) { 969 return false; 970 } 971 if (this.drawBarOutline != that.drawBarOutline) { 972 return false; 973 } 974 if (this.margin != that.margin) { 975 return false; 976 } 977 if (this.useYInterval != that.useYInterval) { 978 return false; 979 } 980 if (!ObjectUtilities.equal( 981 this.gradientPaintTransformer, that.gradientPaintTransformer) 982 ) { 983 return false; 984 } 985 if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) { 986 return false; 987 } 988 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback, 989 that.positiveItemLabelPositionFallback)) { 990 return false; 991 } 992 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback, 993 that.negativeItemLabelPositionFallback)) { 994 return false; 995 } 996 return true; 997 } 998 999 /** 1000 * Provides serialization support. 1001 * 1002 * @param stream the input stream. 1003 * 1004 * @throws IOException if there is an I/O error. 1005 * @throws ClassNotFoundException if there is a classpath problem. 1006 */ 1007 private void readObject(ObjectInputStream stream) 1008 throws IOException, ClassNotFoundException { 1009 stream.defaultReadObject(); 1010 this.legendBar = SerialUtilities.readShape(stream); 1011 } 1012 1013 /** 1014 * Provides serialization support. 1015 * 1016 * @param stream the output stream. 1017 * 1018 * @throws IOException if there is an I/O error. 1019 */ 1020 private void writeObject(ObjectOutputStream stream) throws IOException { 1021 stream.defaultWriteObject(); 1022 SerialUtilities.writeShape(this.legendBar, stream); 1023 } 1024 1025 }