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