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