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