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