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