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