001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------ 028 * XYBarRenderer.java 029 * ------------------ 030 * (C) Copyright 2001-2009, 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 * Marc van Glabbeek (bug 1775452); 037 * Richard West, Advanced Micro Devices, Inc.; 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 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 086 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG); 087 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 088 * 15-Jun-2007 : Changed default for drawBarOutline to false (DG); 089 * 26-Sep-2007 : Fixed bug 1775452, problem with bar margins for inverted 090 * axes, thanks to Marc van Glabbeek (DG); 091 * 12-Nov-2007 : Fixed NPE in drawItemLabel() method, thanks to Richard West 092 * (see patch 1827829) (DG); 093 * 17-Jun-2008 : Apply legend font and paint attributes (DG); 094 * 19-Jun-2008 : Added findRangeBounds() method override to fix bug in default 095 * axis range (DG); 096 * 24-Jun-2008 : Added new barPainter mechanism (DG); 097 * 03-Feb-2009 : Added defaultShadowsVisible flag (DG); 098 * 05-Feb-2009 : Added barAlignmentFactor (DG); 099 * 100 */ 101 102 package org.jfree.chart.renderer.xy; 103 104 import java.awt.Font; 105 import java.awt.Graphics2D; 106 import java.awt.Paint; 107 import java.awt.Shape; 108 import java.awt.Stroke; 109 import java.awt.geom.Point2D; 110 import java.awt.geom.Rectangle2D; 111 import java.io.IOException; 112 import java.io.ObjectInputStream; 113 import java.io.ObjectOutputStream; 114 import java.io.Serializable; 115 116 import org.jfree.chart.LegendItem; 117 import org.jfree.chart.axis.ValueAxis; 118 import org.jfree.chart.entity.EntityCollection; 119 import org.jfree.chart.event.RendererChangeEvent; 120 import org.jfree.chart.labels.ItemLabelAnchor; 121 import org.jfree.chart.labels.ItemLabelPosition; 122 import org.jfree.chart.labels.XYItemLabelGenerator; 123 import org.jfree.chart.labels.XYSeriesLabelGenerator; 124 import org.jfree.chart.plot.CrosshairState; 125 import org.jfree.chart.plot.PlotOrientation; 126 import org.jfree.chart.plot.PlotRenderingInfo; 127 import org.jfree.chart.plot.XYPlot; 128 import org.jfree.data.Range; 129 import org.jfree.data.general.DatasetUtilities; 130 import org.jfree.data.xy.IntervalXYDataset; 131 import org.jfree.data.xy.XYDataset; 132 import org.jfree.io.SerialUtilities; 133 import org.jfree.text.TextUtilities; 134 import org.jfree.ui.GradientPaintTransformer; 135 import org.jfree.ui.RectangleEdge; 136 import org.jfree.ui.StandardGradientPaintTransformer; 137 import org.jfree.util.ObjectUtilities; 138 import org.jfree.util.PublicCloneable; 139 import org.jfree.util.ShapeUtilities; 140 141 /** 142 * A renderer that draws bars on an {@link XYPlot} (requires an 143 * {@link IntervalXYDataset}). The example shown here is generated by the 144 * <code>XYBarChartDemo1.java</code> program included in the JFreeChart 145 * demo collection: 146 * <br><br> 147 * <img src="../../../../../images/XYBarRendererSample.png" 148 * alt="XYBarRendererSample.png" /> 149 */ 150 public class XYBarRenderer extends AbstractXYItemRenderer 151 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 152 153 /** For serialization. */ 154 private static final long serialVersionUID = 770559577251370036L; 155 156 /** 157 * The default bar painter assigned to each new instance of this renderer. 158 * 159 * @since 1.0.11 160 */ 161 private static XYBarPainter defaultBarPainter = new GradientXYBarPainter(); 162 163 /** 164 * Returns the default bar painter. 165 * 166 * @return The default bar painter. 167 * 168 * @since 1.0.11 169 */ 170 public static XYBarPainter getDefaultBarPainter() { 171 return XYBarRenderer.defaultBarPainter; 172 } 173 174 /** 175 * Sets the default bar painter. 176 * 177 * @param painter the painter (<code>null</code> not permitted). 178 * 179 * @since 1.0.11 180 */ 181 public static void setDefaultBarPainter(XYBarPainter painter) { 182 if (painter == null) { 183 throw new IllegalArgumentException("Null 'painter' argument."); 184 } 185 XYBarRenderer.defaultBarPainter = painter; 186 } 187 188 /** 189 * The default value for the initialisation of the shadowsVisible flag. 190 */ 191 private static boolean defaultShadowsVisible = true; 192 193 /** 194 * Returns the default value for the <code>shadowsVisible</code> flag. 195 * 196 * @return A boolean. 197 * 198 * @see #setDefaultShadowsVisible(boolean) 199 * 200 * @since 1.0.13 201 */ 202 public static boolean getDefaultShadowsVisible() { 203 return XYBarRenderer.defaultShadowsVisible; 204 } 205 206 /** 207 * Sets the default value for the shadows visible flag. 208 * 209 * @param visible the new value for the default. 210 * 211 * @see #getDefaultShadowsVisible() 212 * 213 * @since 1.0.13 214 */ 215 public static void setDefaultShadowsVisible(boolean visible) { 216 XYBarRenderer.defaultShadowsVisible = visible; 217 } 218 219 /** 220 * The state class used by this renderer. 221 */ 222 protected class XYBarRendererState extends XYItemRendererState { 223 224 /** Base for bars against the range axis, in Java 2D space. */ 225 private double g2Base; 226 227 /** 228 * Creates a new state object. 229 * 230 * @param info the plot rendering info. 231 */ 232 public XYBarRendererState(PlotRenderingInfo info) { 233 super(info); 234 } 235 236 /** 237 * Returns the base (range) value in Java 2D space. 238 * 239 * @return The base value. 240 */ 241 public double getG2Base() { 242 return this.g2Base; 243 } 244 245 /** 246 * Sets the range axis base in Java2D space. 247 * 248 * @param value the value. 249 */ 250 public void setG2Base(double value) { 251 this.g2Base = value; 252 } 253 } 254 255 /** The default base value for the bars. */ 256 private double base; 257 258 /** 259 * A flag that controls whether the bars use the y-interval supplied by the 260 * dataset. 261 */ 262 private boolean useYInterval; 263 264 /** Percentage margin (to reduce the width of bars). */ 265 private double margin; 266 267 /** A flag that controls whether or not bar outlines are drawn. */ 268 private boolean drawBarOutline; 269 270 /** 271 * An optional class used to transform gradient paint objects to fit each 272 * bar. 273 */ 274 private GradientPaintTransformer gradientPaintTransformer; 275 276 /** 277 * The shape used to represent a bar in each legend item (this should never 278 * be <code>null</code>). 279 */ 280 private transient Shape legendBar; 281 282 /** 283 * The fallback position if a positive item label doesn't fit inside the 284 * bar. 285 */ 286 private ItemLabelPosition positiveItemLabelPositionFallback; 287 288 /** 289 * The fallback position if a negative item label doesn't fit inside the 290 * bar. 291 */ 292 private ItemLabelPosition negativeItemLabelPositionFallback; 293 294 /** 295 * The bar painter (never <code>null</code>). 296 * 297 * @since 1.0.11 298 */ 299 private XYBarPainter barPainter; 300 301 /** 302 * The flag that controls whether or not shadows are drawn for the bars. 303 * 304 * @since 1.0.11 305 */ 306 private boolean shadowsVisible; 307 308 /** 309 * The x-offset for the shadow effect. 310 * 311 * @since 1.0.11 312 */ 313 private double shadowXOffset; 314 315 /** 316 * The y-offset for the shadow effect. 317 * 318 * @since 1.0.11 319 */ 320 private double shadowYOffset; 321 322 /** 323 * A factor used to align the bars about the x-value. 324 * 325 * @since 1.0.13 326 */ 327 private double barAlignmentFactor; 328 329 /** 330 * The default constructor. 331 */ 332 public XYBarRenderer() { 333 this(0.0); 334 } 335 336 /** 337 * Constructs a new renderer. 338 * 339 * @param margin the percentage amount to trim from the width of each bar. 340 */ 341 public XYBarRenderer(double margin) { 342 super(); 343 this.margin = margin; 344 this.base = 0.0; 345 this.useYInterval = false; 346 this.gradientPaintTransformer = new StandardGradientPaintTransformer(); 347 this.drawBarOutline = false; 348 this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0); 349 this.barPainter = getDefaultBarPainter(); 350 this.shadowsVisible = getDefaultShadowsVisible(); 351 this.shadowXOffset = 4.0; 352 this.shadowYOffset = 4.0; 353 this.barAlignmentFactor = -1.0; 354 } 355 356 /** 357 * Returns the base value for the bars. 358 * 359 * @return The base value for the bars. 360 * 361 * @see #setBase(double) 362 */ 363 public double getBase() { 364 return this.base; 365 } 366 367 /** 368 * Sets the base value for the bars and sends a {@link RendererChangeEvent} 369 * to all registered listeners. The base value is not used if the dataset's 370 * y-interval is being used to determine the bar length. 371 * 372 * @param base the new base value. 373 * 374 * @see #getBase() 375 * @see #getUseYInterval() 376 */ 377 public void setBase(double base) { 378 this.base = base; 379 fireChangeEvent(); 380 } 381 382 /** 383 * Returns a flag that determines whether the y-interval from the dataset is 384 * used to calculate the length of each bar. 385 * 386 * @return A boolean. 387 * 388 * @see #setUseYInterval(boolean) 389 */ 390 public boolean getUseYInterval() { 391 return this.useYInterval; 392 } 393 394 /** 395 * Sets the flag that determines whether the y-interval from the dataset is 396 * used to calculate the length of each bar, and sends a 397 * {@link RendererChangeEvent} to all registered listeners. 398 * 399 * @param use the flag. 400 * 401 * @see #getUseYInterval() 402 */ 403 public void setUseYInterval(boolean use) { 404 if (this.useYInterval != use) { 405 this.useYInterval = use; 406 fireChangeEvent(); 407 } 408 } 409 410 /** 411 * Returns the margin which is a percentage amount by which the bars are 412 * trimmed. 413 * 414 * @return The margin. 415 * 416 * @see #setMargin(double) 417 */ 418 public double getMargin() { 419 return this.margin; 420 } 421 422 /** 423 * Sets the percentage amount by which the bars are trimmed and sends a 424 * {@link RendererChangeEvent} to all registered listeners. 425 * 426 * @param margin the new margin. 427 * 428 * @see #getMargin() 429 */ 430 public void setMargin(double margin) { 431 this.margin = margin; 432 fireChangeEvent(); 433 } 434 435 /** 436 * Returns a flag that controls whether or not bar outlines are drawn. 437 * 438 * @return A boolean. 439 * 440 * @see #setDrawBarOutline(boolean) 441 */ 442 public boolean isDrawBarOutline() { 443 return this.drawBarOutline; 444 } 445 446 /** 447 * Sets the flag that controls whether or not bar outlines are drawn and 448 * sends a {@link RendererChangeEvent} to all registered listeners. 449 * 450 * @param draw the flag. 451 * 452 * @see #isDrawBarOutline() 453 */ 454 public void setDrawBarOutline(boolean draw) { 455 this.drawBarOutline = draw; 456 fireChangeEvent(); 457 } 458 459 /** 460 * Returns the gradient paint transformer (an object used to transform 461 * gradient paint objects to fit each bar). 462 * 463 * @return A transformer (<code>null</code> possible). 464 * 465 * @see #setGradientPaintTransformer(GradientPaintTransformer) 466 */ 467 public GradientPaintTransformer getGradientPaintTransformer() { 468 return this.gradientPaintTransformer; 469 } 470 471 /** 472 * Sets the gradient paint transformer and sends a 473 * {@link RendererChangeEvent} to all registered listeners. 474 * 475 * @param transformer the transformer (<code>null</code> permitted). 476 * 477 * @see #getGradientPaintTransformer() 478 */ 479 public void setGradientPaintTransformer( 480 GradientPaintTransformer transformer) { 481 this.gradientPaintTransformer = transformer; 482 fireChangeEvent(); 483 } 484 485 /** 486 * Returns the shape used to represent bars in each legend item. 487 * 488 * @return The shape used to represent bars in each legend item (never 489 * <code>null</code>). 490 * 491 * @see #setLegendBar(Shape) 492 */ 493 public Shape getLegendBar() { 494 return this.legendBar; 495 } 496 497 /** 498 * Sets the shape used to represent bars in each legend item and sends a 499 * {@link RendererChangeEvent} to all registered listeners. 500 * 501 * @param bar the bar shape (<code>null</code> not permitted). 502 * 503 * @see #getLegendBar() 504 */ 505 public void setLegendBar(Shape bar) { 506 if (bar == null) { 507 throw new IllegalArgumentException("Null 'bar' argument."); 508 } 509 this.legendBar = bar; 510 fireChangeEvent(); 511 } 512 513 /** 514 * Returns the fallback position for positive item labels that don't fit 515 * within a bar. 516 * 517 * @return The fallback position (<code>null</code> possible). 518 * 519 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition) 520 * @since 1.0.2 521 */ 522 public ItemLabelPosition getPositiveItemLabelPositionFallback() { 523 return this.positiveItemLabelPositionFallback; 524 } 525 526 /** 527 * Sets the fallback position for positive item labels that don't fit 528 * within a bar, and sends a {@link RendererChangeEvent} to all registered 529 * listeners. 530 * 531 * @param position the position (<code>null</code> permitted). 532 * 533 * @see #getPositiveItemLabelPositionFallback() 534 * @since 1.0.2 535 */ 536 public void setPositiveItemLabelPositionFallback( 537 ItemLabelPosition position) { 538 this.positiveItemLabelPositionFallback = position; 539 fireChangeEvent(); 540 } 541 542 /** 543 * Returns the fallback position for negative item labels that don't fit 544 * within a bar. 545 * 546 * @return The fallback position (<code>null</code> possible). 547 * 548 * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition) 549 * @since 1.0.2 550 */ 551 public ItemLabelPosition getNegativeItemLabelPositionFallback() { 552 return this.negativeItemLabelPositionFallback; 553 } 554 555 /** 556 * Sets the fallback position for negative item labels that don't fit 557 * within a bar, and sends a {@link RendererChangeEvent} to all registered 558 * listeners. 559 * 560 * @param position the position (<code>null</code> permitted). 561 * 562 * @see #getNegativeItemLabelPositionFallback() 563 * @since 1.0.2 564 */ 565 public void setNegativeItemLabelPositionFallback( 566 ItemLabelPosition position) { 567 this.negativeItemLabelPositionFallback = position; 568 fireChangeEvent(); 569 } 570 571 /** 572 * Returns the bar painter. 573 * 574 * @return The bar painter (never <code>null</code>). 575 * 576 * @since 1.0.11 577 */ 578 public XYBarPainter getBarPainter() { 579 return this.barPainter; 580 } 581 582 /** 583 * Sets the bar painter and sends a {@link RendererChangeEvent} to all 584 * registered listeners. 585 * 586 * @param painter the painter (<code>null</code> not permitted). 587 * 588 * @since 1.0.11 589 */ 590 public void setBarPainter(XYBarPainter painter) { 591 if (painter == null) { 592 throw new IllegalArgumentException("Null 'painter' argument."); 593 } 594 this.barPainter = painter; 595 fireChangeEvent(); 596 } 597 598 /** 599 * Returns the flag that controls whether or not shadows are drawn for 600 * the bars. 601 * 602 * @return A boolean. 603 * 604 * @since 1.0.11 605 */ 606 public boolean getShadowsVisible() { 607 return this.shadowsVisible; 608 } 609 610 /** 611 * Sets the flag that controls whether or not the renderer 612 * draws shadows for the bars, and sends a 613 * {@link RendererChangeEvent} to all registered listeners. 614 * 615 * @param visible the new flag value. 616 * 617 * @since 1.0.11 618 */ 619 public void setShadowVisible(boolean visible) { 620 this.shadowsVisible = visible; 621 fireChangeEvent(); 622 } 623 624 /** 625 * Returns the shadow x-offset. 626 * 627 * @return The shadow x-offset. 628 * 629 * @since 1.0.11 630 */ 631 public double getShadowXOffset() { 632 return this.shadowXOffset; 633 } 634 635 /** 636 * Sets the x-offset for the bar shadow and sends a 637 * {@link RendererChangeEvent} to all registered listeners. 638 * 639 * @param offset the offset. 640 * 641 * @since 1.0.11 642 */ 643 public void setShadowXOffset(double offset) { 644 this.shadowXOffset = offset; 645 fireChangeEvent(); 646 } 647 648 /** 649 * Returns the shadow y-offset. 650 * 651 * @return The shadow y-offset. 652 * 653 * @since 1.0.11 654 */ 655 public double getShadowYOffset() { 656 return this.shadowYOffset; 657 } 658 659 /** 660 * Sets the y-offset for the bar shadow and sends a 661 * {@link RendererChangeEvent} to all registered listeners. 662 * 663 * @param offset the offset. 664 * 665 * @since 1.0.11 666 */ 667 public void setShadowYOffset(double offset) { 668 this.shadowYOffset = offset; 669 fireChangeEvent(); 670 } 671 672 /** 673 * Returns the bar alignment factor. 674 * 675 * @return The bar alignment factor. 676 * 677 * @since 1.0.13 678 */ 679 public double getBarAlignmentFactor() { 680 return this.barAlignmentFactor; 681 } 682 683 /** 684 * Sets the bar alignment factor and sends a {@link RendererChangeEvent} 685 * to all registered listeners. If the alignment factor is outside the 686 * range 0.0 to 1.0, no alignment will be performed by the renderer. 687 * 688 * @param factor the factor. 689 * 690 * @since 1.0.13 691 */ 692 public void setBarAlignmentFactor(double factor) { 693 this.barAlignmentFactor = factor; 694 fireChangeEvent(); 695 } 696 697 /** 698 * Initialises the renderer and returns a state object that should be 699 * passed to all subsequent calls to the drawItem() method. Here we 700 * calculate the Java2D y-coordinate for zero, since all the bars have 701 * their bases fixed at zero. 702 * 703 * @param g2 the graphics device. 704 * @param dataArea the area inside the axes. 705 * @param plot the plot. 706 * @param dataset the data. 707 * @param info an optional info collection object to return data back to 708 * the caller. 709 * 710 * @return A state object. 711 */ 712 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 713 XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { 714 715 XYBarRendererState state = new XYBarRendererState(info); 716 ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf( 717 dataset)); 718 state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea, 719 plot.getRangeAxisEdge())); 720 return state; 721 722 } 723 724 /** 725 * Returns a default legend item for the specified series. Subclasses 726 * should override this method to generate customised items. 727 * 728 * @param datasetIndex the dataset index (zero-based). 729 * @param series the series index (zero-based). 730 * 731 * @return A legend item for the series. 732 */ 733 public LegendItem getLegendItem(int datasetIndex, int series) { 734 LegendItem result = null; 735 XYPlot xyplot = getPlot(); 736 if (xyplot != null) { 737 XYDataset dataset = xyplot.getDataset(datasetIndex); 738 if (dataset != null) { 739 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); 740 String label = lg.generateLabel(dataset, series); 741 String description = label; 742 String toolTipText = null; 743 if (getLegendItemToolTipGenerator() != null) { 744 toolTipText = getLegendItemToolTipGenerator().generateLabel( 745 dataset, series); 746 } 747 String urlText = null; 748 if (getLegendItemURLGenerator() != null) { 749 urlText = getLegendItemURLGenerator().generateLabel( 750 dataset, series); 751 } 752 Shape shape = this.legendBar; 753 Paint paint = lookupSeriesPaint(series); 754 Paint outlinePaint = lookupSeriesOutlinePaint(series); 755 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 756 if (this.drawBarOutline) { 757 result = new LegendItem(label, description, toolTipText, 758 urlText, shape, paint, outlineStroke, outlinePaint); 759 } 760 else { 761 result = new LegendItem(label, description, toolTipText, 762 urlText, shape, paint); 763 } 764 result.setLabelFont(lookupLegendTextFont(series)); 765 Paint labelPaint = lookupLegendTextPaint(series); 766 if (labelPaint != null) { 767 result.setLabelPaint(labelPaint); 768 } 769 result.setDataset(dataset); 770 result.setDatasetIndex(datasetIndex); 771 result.setSeriesKey(dataset.getSeriesKey(series)); 772 result.setSeriesIndex(series); 773 if (getGradientPaintTransformer() != null) { 774 result.setFillPaintTransformer( 775 getGradientPaintTransformer()); 776 } 777 } 778 } 779 return result; 780 } 781 782 /** 783 * Draws the visual representation of a single data item. 784 * 785 * @param g2 the graphics device. 786 * @param state the renderer state. 787 * @param dataArea the area within which the plot is being drawn. 788 * @param info collects information about the drawing. 789 * @param plot the plot (can be used to obtain standard color 790 * information etc). 791 * @param domainAxis the domain axis. 792 * @param rangeAxis the range axis. 793 * @param dataset the dataset. 794 * @param series the series index (zero-based). 795 * @param item the item index (zero-based). 796 * @param crosshairState crosshair information for the plot 797 * (<code>null</code> permitted). 798 * @param pass the pass index. 799 */ 800 public void drawItem(Graphics2D g2, 801 XYItemRendererState state, 802 Rectangle2D dataArea, 803 PlotRenderingInfo info, 804 XYPlot plot, 805 ValueAxis domainAxis, 806 ValueAxis rangeAxis, 807 XYDataset dataset, 808 int series, 809 int item, 810 CrosshairState crosshairState, 811 int pass) { 812 813 if (!getItemVisible(series, item)) { 814 return; 815 } 816 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 817 818 double value0; 819 double value1; 820 if (this.useYInterval) { 821 value0 = intervalDataset.getStartYValue(series, item); 822 value1 = intervalDataset.getEndYValue(series, item); 823 } 824 else { 825 value0 = this.base; 826 value1 = intervalDataset.getYValue(series, item); 827 } 828 if (Double.isNaN(value0) || Double.isNaN(value1)) { 829 return; 830 } 831 if (value0 <= value1) { 832 if (!rangeAxis.getRange().intersects(value0, value1)) { 833 return; 834 } 835 } 836 else { 837 if (!rangeAxis.getRange().intersects(value1, value0)) { 838 return; 839 } 840 } 841 842 double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea, 843 plot.getRangeAxisEdge()); 844 double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea, 845 plot.getRangeAxisEdge()); 846 double bottom = Math.min(translatedValue0, translatedValue1); 847 double top = Math.max(translatedValue0, translatedValue1); 848 849 double startX = intervalDataset.getStartXValue(series, item); 850 if (Double.isNaN(startX)) { 851 return; 852 } 853 double endX = intervalDataset.getEndXValue(series, item); 854 if (Double.isNaN(endX)) { 855 return; 856 } 857 if (startX <= endX) { 858 if (!domainAxis.getRange().intersects(startX, endX)) { 859 return; 860 } 861 } 862 else { 863 if (!domainAxis.getRange().intersects(endX, startX)) { 864 return; 865 } 866 } 867 868 // is there an alignment adjustment to be made? 869 if (this.barAlignmentFactor >= 0.0 && this.barAlignmentFactor <= 1.0) { 870 double x = intervalDataset.getXValue(series, item); 871 double interval = endX - startX; 872 startX = x - interval * this.barAlignmentFactor; 873 endX = startX + interval; 874 } 875 876 RectangleEdge location = plot.getDomainAxisEdge(); 877 double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, 878 location); 879 double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, 880 location); 881 882 double translatedWidth = Math.max(1, Math.abs(translatedEndX 883 - translatedStartX)); 884 885 double left = Math.min(translatedStartX, translatedEndX); 886 if (getMargin() > 0.0) { 887 double cut = translatedWidth * getMargin(); 888 translatedWidth = translatedWidth - cut; 889 left = left + cut / 2; 890 } 891 892 Rectangle2D bar = null; 893 PlotOrientation orientation = plot.getOrientation(); 894 if (orientation == PlotOrientation.HORIZONTAL) { 895 // clip left and right bounds to data area 896 bottom = Math.max(bottom, dataArea.getMinX()); 897 top = Math.min(top, dataArea.getMaxX()); 898 bar = new Rectangle2D.Double( 899 bottom, left, top - bottom, translatedWidth); 900 } 901 else if (orientation == PlotOrientation.VERTICAL) { 902 // clip top and bottom bounds to data area 903 bottom = Math.max(bottom, dataArea.getMinY()); 904 top = Math.min(top, dataArea.getMaxY()); 905 bar = new Rectangle2D.Double(left, bottom, translatedWidth, 906 top - bottom); 907 } 908 909 boolean positive = (value1 > 0.0); 910 boolean inverted = rangeAxis.isInverted(); 911 RectangleEdge barBase; 912 if (orientation == PlotOrientation.HORIZONTAL) { 913 if (positive && inverted || !positive && !inverted) { 914 barBase = RectangleEdge.RIGHT; 915 } 916 else { 917 barBase = RectangleEdge.LEFT; 918 } 919 } 920 else { 921 if (positive && !inverted || !positive && inverted) { 922 barBase = RectangleEdge.BOTTOM; 923 } 924 else { 925 barBase = RectangleEdge.TOP; 926 } 927 } 928 if (getShadowsVisible()) { 929 this.barPainter.paintBarShadow(g2, this, series, item, bar, barBase, 930 !this.useYInterval); 931 } 932 this.barPainter.paintBar(g2, this, series, item, bar, barBase); 933 934 if (isItemLabelVisible(series, item)) { 935 XYItemLabelGenerator generator = getItemLabelGenerator(series, 936 item); 937 drawItemLabel(g2, dataset, series, item, plot, generator, bar, 938 value1 < 0.0); 939 } 940 941 // update the crosshair point 942 double x1 = (startX + endX) / 2.0; 943 double y1 = dataset.getYValue(series, item); 944 double transX1 = domainAxis.valueToJava2D(x1, dataArea, location); 945 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 946 plot.getRangeAxisEdge()); 947 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 948 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 949 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 950 rangeAxisIndex, transX1, transY1, plot.getOrientation()); 951 952 EntityCollection entities = state.getEntityCollection(); 953 if (entities != null) { 954 addEntity(entities, bar, dataset, series, item, 0.0, 0.0); 955 } 956 957 } 958 959 /** 960 * Draws an item label. This method is provided as an alternative to 961 * {@link #drawItemLabel(Graphics2D, PlotOrientation, XYDataset, int, int, 962 * double, double, boolean)} so that the bar can be used to calculate the 963 * label anchor point. 964 * 965 * @param g2 the graphics device. 966 * @param dataset the dataset. 967 * @param series the series index. 968 * @param item the item index. 969 * @param plot the plot. 970 * @param generator the label generator (<code>null</code> permitted, in 971 * which case the method does nothing, just returns). 972 * @param bar the bar. 973 * @param negative a flag indicating a negative value. 974 */ 975 protected void drawItemLabel(Graphics2D g2, XYDataset dataset, 976 int series, int item, XYPlot plot, XYItemLabelGenerator generator, 977 Rectangle2D bar, boolean negative) { 978 979 if (generator == null) { 980 return; // nothing to do 981 } 982 String label = generator.generateLabel(dataset, series, item); 983 if (label == null) { 984 return; // nothing to do 985 } 986 987 Font labelFont = getItemLabelFont(series, item); 988 g2.setFont(labelFont); 989 Paint paint = getItemLabelPaint(series, item); 990 g2.setPaint(paint); 991 992 // find out where to place the label... 993 ItemLabelPosition position = null; 994 if (!negative) { 995 position = getPositiveItemLabelPosition(series, item); 996 } 997 else { 998 position = getNegativeItemLabelPosition(series, item); 999 } 1000 1001 // work out the label anchor point... 1002 Point2D anchorPoint = calculateLabelAnchorPoint( 1003 position.getItemLabelAnchor(), bar, plot.getOrientation()); 1004 1005 if (isInternalAnchor(position.getItemLabelAnchor())) { 1006 Shape bounds = TextUtilities.calculateRotatedStringBounds(label, 1007 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), 1008 position.getTextAnchor(), position.getAngle(), 1009 position.getRotationAnchor()); 1010 1011 if (bounds != null) { 1012 if (!bar.contains(bounds.getBounds2D())) { 1013 if (!negative) { 1014 position = getPositiveItemLabelPositionFallback(); 1015 } 1016 else { 1017 position = getNegativeItemLabelPositionFallback(); 1018 } 1019 if (position != null) { 1020 anchorPoint = calculateLabelAnchorPoint( 1021 position.getItemLabelAnchor(), bar, 1022 plot.getOrientation()); 1023 } 1024 } 1025 } 1026 1027 } 1028 1029 if (position != null) { 1030 TextUtilities.drawRotatedString(label, g2, 1031 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 1032 position.getTextAnchor(), position.getAngle(), 1033 position.getRotationAnchor()); 1034 } 1035 } 1036 1037 /** 1038 * Calculates the item label anchor point. 1039 * 1040 * @param anchor the anchor. 1041 * @param bar the bar. 1042 * @param orientation the plot orientation. 1043 * 1044 * @return The anchor point. 1045 */ 1046 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor, 1047 Rectangle2D bar, PlotOrientation orientation) { 1048 1049 Point2D result = null; 1050 double offset = getItemLabelAnchorOffset(); 1051 double x0 = bar.getX() - offset; 1052 double x1 = bar.getX(); 1053 double x2 = bar.getX() + offset; 1054 double x3 = bar.getCenterX(); 1055 double x4 = bar.getMaxX() - offset; 1056 double x5 = bar.getMaxX(); 1057 double x6 = bar.getMaxX() + offset; 1058 1059 double y0 = bar.getMaxY() + offset; 1060 double y1 = bar.getMaxY(); 1061 double y2 = bar.getMaxY() - offset; 1062 double y3 = bar.getCenterY(); 1063 double y4 = bar.getMinY() + offset; 1064 double y5 = bar.getMinY(); 1065 double y6 = bar.getMinY() - offset; 1066 1067 if (anchor == ItemLabelAnchor.CENTER) { 1068 result = new Point2D.Double(x3, y3); 1069 } 1070 else if (anchor == ItemLabelAnchor.INSIDE1) { 1071 result = new Point2D.Double(x4, y4); 1072 } 1073 else if (anchor == ItemLabelAnchor.INSIDE2) { 1074 result = new Point2D.Double(x4, y4); 1075 } 1076 else if (anchor == ItemLabelAnchor.INSIDE3) { 1077 result = new Point2D.Double(x4, y3); 1078 } 1079 else if (anchor == ItemLabelAnchor.INSIDE4) { 1080 result = new Point2D.Double(x4, y2); 1081 } 1082 else if (anchor == ItemLabelAnchor.INSIDE5) { 1083 result = new Point2D.Double(x4, y2); 1084 } 1085 else if (anchor == ItemLabelAnchor.INSIDE6) { 1086 result = new Point2D.Double(x3, y2); 1087 } 1088 else if (anchor == ItemLabelAnchor.INSIDE7) { 1089 result = new Point2D.Double(x2, y2); 1090 } 1091 else if (anchor == ItemLabelAnchor.INSIDE8) { 1092 result = new Point2D.Double(x2, y2); 1093 } 1094 else if (anchor == ItemLabelAnchor.INSIDE9) { 1095 result = new Point2D.Double(x2, y3); 1096 } 1097 else if (anchor == ItemLabelAnchor.INSIDE10) { 1098 result = new Point2D.Double(x2, y4); 1099 } 1100 else if (anchor == ItemLabelAnchor.INSIDE11) { 1101 result = new Point2D.Double(x2, y4); 1102 } 1103 else if (anchor == ItemLabelAnchor.INSIDE12) { 1104 result = new Point2D.Double(x3, y4); 1105 } 1106 else if (anchor == ItemLabelAnchor.OUTSIDE1) { 1107 result = new Point2D.Double(x5, y6); 1108 } 1109 else if (anchor == ItemLabelAnchor.OUTSIDE2) { 1110 result = new Point2D.Double(x6, y5); 1111 } 1112 else if (anchor == ItemLabelAnchor.OUTSIDE3) { 1113 result = new Point2D.Double(x6, y3); 1114 } 1115 else if (anchor == ItemLabelAnchor.OUTSIDE4) { 1116 result = new Point2D.Double(x6, y1); 1117 } 1118 else if (anchor == ItemLabelAnchor.OUTSIDE5) { 1119 result = new Point2D.Double(x5, y0); 1120 } 1121 else if (anchor == ItemLabelAnchor.OUTSIDE6) { 1122 result = new Point2D.Double(x3, y0); 1123 } 1124 else if (anchor == ItemLabelAnchor.OUTSIDE7) { 1125 result = new Point2D.Double(x1, y0); 1126 } 1127 else if (anchor == ItemLabelAnchor.OUTSIDE8) { 1128 result = new Point2D.Double(x0, y1); 1129 } 1130 else if (anchor == ItemLabelAnchor.OUTSIDE9) { 1131 result = new Point2D.Double(x0, y3); 1132 } 1133 else if (anchor == ItemLabelAnchor.OUTSIDE10) { 1134 result = new Point2D.Double(x0, y5); 1135 } 1136 else if (anchor == ItemLabelAnchor.OUTSIDE11) { 1137 result = new Point2D.Double(x1, y6); 1138 } 1139 else if (anchor == ItemLabelAnchor.OUTSIDE12) { 1140 result = new Point2D.Double(x3, y6); 1141 } 1142 1143 return result; 1144 1145 } 1146 1147 /** 1148 * Returns <code>true</code> if the specified anchor point is inside a bar. 1149 * 1150 * @param anchor the anchor point. 1151 * 1152 * @return A boolean. 1153 */ 1154 private boolean isInternalAnchor(ItemLabelAnchor anchor) { 1155 return anchor == ItemLabelAnchor.CENTER 1156 || anchor == ItemLabelAnchor.INSIDE1 1157 || anchor == ItemLabelAnchor.INSIDE2 1158 || anchor == ItemLabelAnchor.INSIDE3 1159 || anchor == ItemLabelAnchor.INSIDE4 1160 || anchor == ItemLabelAnchor.INSIDE5 1161 || anchor == ItemLabelAnchor.INSIDE6 1162 || anchor == ItemLabelAnchor.INSIDE7 1163 || anchor == ItemLabelAnchor.INSIDE8 1164 || anchor == ItemLabelAnchor.INSIDE9 1165 || anchor == ItemLabelAnchor.INSIDE10 1166 || anchor == ItemLabelAnchor.INSIDE11 1167 || anchor == ItemLabelAnchor.INSIDE12; 1168 } 1169 1170 /** 1171 * Returns the lower and upper bounds (range) of the x-values in the 1172 * specified dataset. Since this renderer uses the x-interval in the 1173 * dataset, this is taken into account for the range. 1174 * 1175 * @param dataset the dataset (<code>null</code> permitted). 1176 * 1177 * @return The range (<code>null</code> if the dataset is 1178 * <code>null</code> or empty). 1179 */ 1180 public Range findDomainBounds(XYDataset dataset) { 1181 if (dataset != null) { 1182 return DatasetUtilities.findDomainBounds(dataset, true); 1183 } 1184 else { 1185 return null; 1186 } 1187 } 1188 1189 /** 1190 * Returns the lower and upper bounds (range) of the y-values in the 1191 * specified dataset. If the renderer is plotting the y-interval from the 1192 * dataset, this is taken into account for the range. 1193 * 1194 * @param dataset the dataset (<code>null</code> permitted). 1195 * 1196 * @return The range (<code>null</code> if the dataset is 1197 * <code>null</code> or empty). 1198 */ 1199 public Range findRangeBounds(XYDataset dataset) { 1200 if (dataset != null) { 1201 return DatasetUtilities.findRangeBounds(dataset, 1202 this.useYInterval); 1203 } 1204 else { 1205 return null; 1206 } 1207 } 1208 1209 /** 1210 * Returns a clone of the renderer. 1211 * 1212 * @return A clone. 1213 * 1214 * @throws CloneNotSupportedException if the renderer cannot be cloned. 1215 */ 1216 public Object clone() throws CloneNotSupportedException { 1217 XYBarRenderer result = (XYBarRenderer) super.clone(); 1218 if (this.gradientPaintTransformer != null) { 1219 result.gradientPaintTransformer = (GradientPaintTransformer) 1220 ObjectUtilities.clone(this.gradientPaintTransformer); 1221 } 1222 result.legendBar = ShapeUtilities.clone(this.legendBar); 1223 return result; 1224 } 1225 1226 /** 1227 * Tests this renderer for equality with an arbitrary object. 1228 * 1229 * @param obj the object to test against (<code>null</code> permitted). 1230 * 1231 * @return A boolean. 1232 */ 1233 public boolean equals(Object obj) { 1234 if (obj == this) { 1235 return true; 1236 } 1237 if (!(obj instanceof XYBarRenderer)) { 1238 return false; 1239 } 1240 XYBarRenderer that = (XYBarRenderer) obj; 1241 if (this.base != that.base) { 1242 return false; 1243 } 1244 if (this.drawBarOutline != that.drawBarOutline) { 1245 return false; 1246 } 1247 if (this.margin != that.margin) { 1248 return false; 1249 } 1250 if (this.useYInterval != that.useYInterval) { 1251 return false; 1252 } 1253 if (!ObjectUtilities.equal( 1254 this.gradientPaintTransformer, that.gradientPaintTransformer) 1255 ) { 1256 return false; 1257 } 1258 if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) { 1259 return false; 1260 } 1261 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback, 1262 that.positiveItemLabelPositionFallback)) { 1263 return false; 1264 } 1265 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback, 1266 that.negativeItemLabelPositionFallback)) { 1267 return false; 1268 } 1269 if (!this.barPainter.equals(that.barPainter)) { 1270 return false; 1271 } 1272 if (this.shadowsVisible != that.shadowsVisible) { 1273 return false; 1274 } 1275 if (this.shadowXOffset != that.shadowXOffset) { 1276 return false; 1277 } 1278 if (this.shadowYOffset != that.shadowYOffset) { 1279 return false; 1280 } 1281 if (this.barAlignmentFactor != that.barAlignmentFactor) { 1282 return false; 1283 } 1284 return super.equals(obj); 1285 } 1286 1287 /** 1288 * Provides serialization support. 1289 * 1290 * @param stream the input stream. 1291 * 1292 * @throws IOException if there is an I/O error. 1293 * @throws ClassNotFoundException if there is a classpath problem. 1294 */ 1295 private void readObject(ObjectInputStream stream) 1296 throws IOException, ClassNotFoundException { 1297 stream.defaultReadObject(); 1298 this.legendBar = SerialUtilities.readShape(stream); 1299 } 1300 1301 /** 1302 * Provides serialization support. 1303 * 1304 * @param stream the output stream. 1305 * 1306 * @throws IOException if there is an I/O error. 1307 */ 1308 private void writeObject(ObjectOutputStream stream) throws IOException { 1309 stream.defaultWriteObject(); 1310 SerialUtilities.writeShape(this.legendBar, stream); 1311 } 1312 1313 }