001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------ 028 * XYBarRenderer.java 029 * ------------------ 030 * (C) Copyright 2001-2007, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Richard Atkinson; 034 * Christian W. Zuckschwerdt; 035 * Bill Kelemen; 036 * Marc van Glabbeek (bug 1775452); 037 * Richard West, Advanced Micro Devices, Inc.; 038 * 039 * Changes 040 * ------- 041 * 13-Dec-2001 : Version 1, makes VerticalXYBarPlot class redundant (DG); 042 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 043 * 09-Apr-2002 : Removed the translated zero from the drawItem method. Override 044 * the initialise() method to calculate it (DG); 045 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 046 * 25-Jun-2002 : Removed redundant import (DG); 047 * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML 048 * image maps (RA); 049 * 25-Mar-2003 : Implemented Serializable (DG); 050 * 01-May-2003 : Modified drawItem() method signature (DG); 051 * 30-Jul-2003 : Modified entity constructor (CZ); 052 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 053 * 24-Aug-2003 : Added null checks in drawItem (BK); 054 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 055 * 07-Oct-2003 : Added renderer state (DG); 056 * 05-Dec-2003 : Changed call to obtain outline paint (DG); 057 * 10-Feb-2004 : Added state class, updated drawItem() method to make 058 * cut-and-paste overriding easier, and replaced property change 059 * with RendererChangeEvent (DG); 060 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 061 * 26-Apr-2004 : Added gradient paint transformer (DG); 062 * 19-May-2004 : Fixed bug (879709) with bar zero value for secondary axis (DG); 063 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 064 * getYValue() (DG); 065 * 01-Sep-2004 : Added a flag to control whether or not the bar outlines are 066 * drawn (DG); 067 * 03-Sep-2004 : Added option to use y-interval from dataset to determine the 068 * length of the bars (DG); 069 * 08-Sep-2004 : Added equals() method and updated clone() method (DG); 070 * 26-Jan-2005 : Added override for getLegendItem() method (DG); 071 * 20-Apr-2005 : Use generators for label tooltips and URLs (DG); 072 * 19-May-2005 : Added minimal item label implementation - needs improving (DG); 073 * 14-Oct-2005 : Fixed rendering problem with inverted axes (DG); 074 * ------------- JFREECHART 1.0.x --------------------------------------------- 075 * 21-Jun-2006 : Improved item label handling - see bug 1501768 (DG); 076 * 24-Aug-2006 : Added crosshair support (DG); 077 * 13-Dec-2006 : Updated getLegendItems() to return gradient paint 078 * transformer (DG); 079 * 02-Feb-2007 : Changed setUseYInterval() to only notify when the flag 080 * changes (DG); 081 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); 082 * 09-Feb-2007 : Updated getLegendItem() to observe drawBarOutline flag (DG); 083 * 05-Mar-2007 : Applied patch 1671126 by Sergei Ivanov, to fix rendering with 084 * LogarithmicAxis (DG); 085 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 086 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG); 087 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 088 * 15-Jun-2007 : Changed default for drawBarOutline to false (DG); 089 * 26-Sep-2007 : Fixed bug 1775452, problem with bar margins for inverted 090 * axes, thanks to Marc van Glabbeek (DG); 091 * 12-Nov-2007 : Fixed NPE in drawItemLabel() method, thanks to Richard West 092 * (see patch 1827829) (DG); 093 * 094 */ 095 096 package org.jfree.chart.renderer.xy; 097 098 import java.awt.Font; 099 import java.awt.GradientPaint; 100 import java.awt.Graphics2D; 101 import java.awt.Paint; 102 import java.awt.Shape; 103 import java.awt.Stroke; 104 import java.awt.geom.Point2D; 105 import java.awt.geom.Rectangle2D; 106 import java.io.IOException; 107 import java.io.ObjectInputStream; 108 import java.io.ObjectOutputStream; 109 import java.io.Serializable; 110 111 import org.jfree.chart.LegendItem; 112 import org.jfree.chart.axis.ValueAxis; 113 import org.jfree.chart.entity.EntityCollection; 114 import org.jfree.chart.event.RendererChangeEvent; 115 import org.jfree.chart.labels.ItemLabelAnchor; 116 import org.jfree.chart.labels.ItemLabelPosition; 117 import org.jfree.chart.labels.XYItemLabelGenerator; 118 import org.jfree.chart.labels.XYSeriesLabelGenerator; 119 import org.jfree.chart.plot.CrosshairState; 120 import org.jfree.chart.plot.PlotOrientation; 121 import org.jfree.chart.plot.PlotRenderingInfo; 122 import org.jfree.chart.plot.XYPlot; 123 import org.jfree.data.Range; 124 import org.jfree.data.general.DatasetUtilities; 125 import org.jfree.data.xy.IntervalXYDataset; 126 import org.jfree.data.xy.XYDataset; 127 import org.jfree.io.SerialUtilities; 128 import org.jfree.text.TextUtilities; 129 import org.jfree.ui.GradientPaintTransformer; 130 import org.jfree.ui.RectangleEdge; 131 import org.jfree.ui.StandardGradientPaintTransformer; 132 import org.jfree.util.ObjectUtilities; 133 import org.jfree.util.PublicCloneable; 134 import org.jfree.util.ShapeUtilities; 135 136 /** 137 * A renderer that draws bars on an {@link XYPlot} (requires an 138 * {@link IntervalXYDataset}). 139 */ 140 public class XYBarRenderer extends AbstractXYItemRenderer 141 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 142 143 /** For serialization. */ 144 private static final long serialVersionUID = 770559577251370036L; 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</code>). 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 default constructor. 223 */ 224 public XYBarRenderer() { 225 this(0.0); 226 } 227 228 /** 229 * Constructs a new renderer. 230 * 231 * @param margin the percentage amount to trim from the width of each bar. 232 */ 233 public XYBarRenderer(double margin) { 234 super(); 235 this.margin = margin; 236 this.base = 0.0; 237 this.useYInterval = false; 238 this.gradientPaintTransformer = new StandardGradientPaintTransformer(); 239 this.drawBarOutline = false; 240 this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0); 241 } 242 243 /** 244 * Returns the base value for the bars. 245 * 246 * @return The base value for the bars. 247 * 248 * @see #setBase(double) 249 */ 250 public double getBase() { 251 return this.base; 252 } 253 254 /** 255 * Sets the base value for the bars and sends a {@link RendererChangeEvent} 256 * to all registered listeners. The base value is not used if the dataset's 257 * y-interval is being used to determine the bar length. 258 * 259 * @param base the new base value. 260 * 261 * @see #getBase() 262 * @see #getUseYInterval() 263 */ 264 public void setBase(double base) { 265 this.base = base; 266 fireChangeEvent(); 267 } 268 269 /** 270 * Returns a flag that determines whether the y-interval from the dataset is 271 * used to calculate the length of each bar. 272 * 273 * @return A boolean. 274 * 275 * @see #setUseYInterval(boolean) 276 */ 277 public boolean getUseYInterval() { 278 return this.useYInterval; 279 } 280 281 /** 282 * Sets the flag that determines whether the y-interval from the dataset is 283 * used to calculate the length of each bar, and sends a 284 * {@link RendererChangeEvent} to all registered listeners. 285 * 286 * @param use the flag. 287 * 288 * @see #getUseYInterval() 289 */ 290 public void setUseYInterval(boolean use) { 291 if (this.useYInterval != use) { 292 this.useYInterval = use; 293 fireChangeEvent(); 294 } 295 } 296 297 /** 298 * Returns the margin which is a percentage amount by which the bars are 299 * trimmed. 300 * 301 * @return The margin. 302 * 303 * @see #setMargin(double) 304 */ 305 public double getMargin() { 306 return this.margin; 307 } 308 309 /** 310 * Sets the percentage amount by which the bars are trimmed and sends a 311 * {@link RendererChangeEvent} to all registered listeners. 312 * 313 * @param margin the new margin. 314 * 315 * @see #getMargin() 316 */ 317 public void setMargin(double margin) { 318 this.margin = margin; 319 fireChangeEvent(); 320 } 321 322 /** 323 * Returns a flag that controls whether or not bar outlines are drawn. 324 * 325 * @return A boolean. 326 * 327 * @see #setDrawBarOutline(boolean) 328 */ 329 public boolean isDrawBarOutline() { 330 return this.drawBarOutline; 331 } 332 333 /** 334 * Sets the flag that controls whether or not bar outlines are drawn and 335 * sends a {@link RendererChangeEvent} to all registered listeners. 336 * 337 * @param draw the flag. 338 * 339 * @see #isDrawBarOutline() 340 */ 341 public void setDrawBarOutline(boolean draw) { 342 this.drawBarOutline = draw; 343 fireChangeEvent(); 344 } 345 346 /** 347 * Returns the gradient paint transformer (an object used to transform 348 * gradient paint objects to fit each bar). 349 * 350 * @return A transformer (<code>null</code> possible). 351 * 352 * @see #setGradientPaintTransformer(GradientPaintTransformer) 353 */ 354 public GradientPaintTransformer getGradientPaintTransformer() { 355 return this.gradientPaintTransformer; 356 } 357 358 /** 359 * Sets the gradient paint transformer and sends a 360 * {@link RendererChangeEvent} to all registered listeners. 361 * 362 * @param transformer the transformer (<code>null</code> permitted). 363 * 364 * @see #getGradientPaintTransformer() 365 */ 366 public void setGradientPaintTransformer( 367 GradientPaintTransformer transformer) { 368 this.gradientPaintTransformer = transformer; 369 fireChangeEvent(); 370 } 371 372 /** 373 * Returns the shape used to represent bars in each legend item. 374 * 375 * @return The shape used to represent bars in each legend item (never 376 * <code>null</code>). 377 * 378 * @see #setLegendBar(Shape) 379 */ 380 public Shape getLegendBar() { 381 return this.legendBar; 382 } 383 384 /** 385 * Sets the shape used to represent bars in each legend item and sends a 386 * {@link RendererChangeEvent} to all registered listeners. 387 * 388 * @param bar the bar shape (<code>null</code> not permitted). 389 * 390 * @see #getLegendBar() 391 */ 392 public void setLegendBar(Shape bar) { 393 if (bar == null) { 394 throw new IllegalArgumentException("Null 'bar' argument."); 395 } 396 this.legendBar = bar; 397 fireChangeEvent(); 398 } 399 400 /** 401 * Returns the fallback position for positive item labels that don't fit 402 * within a bar. 403 * 404 * @return The fallback position (<code>null</code> possible). 405 * 406 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition) 407 * @since 1.0.2 408 */ 409 public ItemLabelPosition getPositiveItemLabelPositionFallback() { 410 return this.positiveItemLabelPositionFallback; 411 } 412 413 /** 414 * Sets the fallback position for positive item labels that don't fit 415 * within a bar, and sends a {@link RendererChangeEvent} to all registered 416 * listeners. 417 * 418 * @param position the position (<code>null</code> permitted). 419 * 420 * @see #getPositiveItemLabelPositionFallback() 421 * @since 1.0.2 422 */ 423 public void setPositiveItemLabelPositionFallback( 424 ItemLabelPosition position) { 425 this.positiveItemLabelPositionFallback = position; 426 fireChangeEvent(); 427 } 428 429 /** 430 * Returns the fallback position for negative item labels that don't fit 431 * within a bar. 432 * 433 * @return The fallback position (<code>null</code> possible). 434 * 435 * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition) 436 * @since 1.0.2 437 */ 438 public ItemLabelPosition getNegativeItemLabelPositionFallback() { 439 return this.negativeItemLabelPositionFallback; 440 } 441 442 /** 443 * Sets the fallback position for negative item labels that don't fit 444 * within a bar, and sends a {@link RendererChangeEvent} to all registered 445 * listeners. 446 * 447 * @param position the position (<code>null</code> permitted). 448 * 449 * @see #getNegativeItemLabelPositionFallback() 450 * @since 1.0.2 451 */ 452 public void setNegativeItemLabelPositionFallback( 453 ItemLabelPosition position) { 454 this.negativeItemLabelPositionFallback = position; 455 fireChangeEvent(); 456 } 457 458 /** 459 * Initialises the renderer and returns a state object that should be 460 * passed to all subsequent calls to the drawItem() method. Here we 461 * calculate the Java2D y-coordinate for zero, since all the bars have 462 * their bases fixed at zero. 463 * 464 * @param g2 the graphics device. 465 * @param dataArea the area inside the axes. 466 * @param plot the plot. 467 * @param dataset the data. 468 * @param info an optional info collection object to return data back to 469 * the caller. 470 * 471 * @return A state object. 472 */ 473 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 474 XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { 475 476 XYBarRendererState state = new XYBarRendererState(info); 477 ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf( 478 dataset)); 479 state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea, 480 plot.getRangeAxisEdge())); 481 return state; 482 483 } 484 485 /** 486 * Returns a default legend item for the specified series. Subclasses 487 * should override this method to generate customised items. 488 * 489 * @param datasetIndex the dataset index (zero-based). 490 * @param series the series index (zero-based). 491 * 492 * @return A legend item for the series. 493 */ 494 public LegendItem getLegendItem(int datasetIndex, int series) { 495 LegendItem result = null; 496 XYPlot xyplot = getPlot(); 497 if (xyplot != null) { 498 XYDataset dataset = xyplot.getDataset(datasetIndex); 499 if (dataset != null) { 500 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); 501 String label = lg.generateLabel(dataset, series); 502 String description = label; 503 String toolTipText = null; 504 if (getLegendItemToolTipGenerator() != null) { 505 toolTipText = getLegendItemToolTipGenerator().generateLabel( 506 dataset, series); 507 } 508 String urlText = null; 509 if (getLegendItemURLGenerator() != null) { 510 urlText = getLegendItemURLGenerator().generateLabel( 511 dataset, series); 512 } 513 Shape shape = this.legendBar; 514 Paint paint = lookupSeriesPaint(series); 515 Paint outlinePaint = lookupSeriesOutlinePaint(series); 516 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 517 if (this.drawBarOutline) { 518 result = new LegendItem(label, description, toolTipText, 519 urlText, shape, paint, outlineStroke, outlinePaint); 520 } 521 else { 522 result = new LegendItem(label, description, toolTipText, 523 urlText, shape, paint); 524 } 525 result.setDataset(dataset); 526 result.setDatasetIndex(datasetIndex); 527 result.setSeriesKey(dataset.getSeriesKey(series)); 528 result.setSeriesIndex(series); 529 if (getGradientPaintTransformer() != null) { 530 result.setFillPaintTransformer( 531 getGradientPaintTransformer()); 532 } 533 } 534 } 535 return result; 536 } 537 538 /** 539 * Draws the visual representation of a single data item. 540 * 541 * @param g2 the graphics device. 542 * @param state the renderer state. 543 * @param dataArea the area within which the plot is being drawn. 544 * @param info collects information about the drawing. 545 * @param plot the plot (can be used to obtain standard color 546 * information etc). 547 * @param domainAxis the domain axis. 548 * @param rangeAxis the range axis. 549 * @param dataset the dataset. 550 * @param series the series index (zero-based). 551 * @param item the item index (zero-based). 552 * @param crosshairState crosshair information for the plot 553 * (<code>null</code> permitted). 554 * @param pass the pass index. 555 */ 556 public void drawItem(Graphics2D g2, 557 XYItemRendererState state, 558 Rectangle2D dataArea, 559 PlotRenderingInfo info, 560 XYPlot plot, 561 ValueAxis domainAxis, 562 ValueAxis rangeAxis, 563 XYDataset dataset, 564 int series, 565 int item, 566 CrosshairState crosshairState, 567 int pass) { 568 569 if (!getItemVisible(series, item)) { 570 return; 571 } 572 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 573 574 double value0; 575 double value1; 576 if (this.useYInterval) { 577 value0 = intervalDataset.getStartYValue(series, item); 578 value1 = intervalDataset.getEndYValue(series, item); 579 } 580 else { 581 value0 = this.base; 582 value1 = intervalDataset.getYValue(series, item); 583 } 584 if (Double.isNaN(value0) || Double.isNaN(value1)) { 585 return; 586 } 587 if (value0 <= value1) { 588 if (!rangeAxis.getRange().intersects(value0, value1)) { 589 return; 590 } 591 } 592 else { 593 if (!rangeAxis.getRange().intersects(value1, value0)) { 594 return; 595 } 596 } 597 598 double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea, 599 plot.getRangeAxisEdge()); 600 double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea, 601 plot.getRangeAxisEdge()); 602 double bottom = Math.min(translatedValue0, translatedValue1); 603 double top = Math.max(translatedValue0, translatedValue1); 604 605 double startX = intervalDataset.getStartXValue(series, item); 606 if (Double.isNaN(startX)) { 607 return; 608 } 609 double endX = intervalDataset.getEndXValue(series, item); 610 if (Double.isNaN(endX)) { 611 return; 612 } 613 if (startX <= endX) { 614 if (!domainAxis.getRange().intersects(startX, endX)) { 615 return; 616 } 617 } 618 else { 619 if (!domainAxis.getRange().intersects(endX, startX)) { 620 return; 621 } 622 } 623 624 RectangleEdge location = plot.getDomainAxisEdge(); 625 double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, 626 location); 627 double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, 628 location); 629 630 double translatedWidth = Math.max(1, Math.abs(translatedEndX 631 - translatedStartX)); 632 633 double left = Math.min(translatedStartX, translatedEndX); 634 if (getMargin() > 0.0) { 635 double cut = translatedWidth * getMargin(); 636 translatedWidth = translatedWidth - cut; 637 left = left + cut / 2; 638 } 639 640 Rectangle2D bar = null; 641 PlotOrientation orientation = plot.getOrientation(); 642 if (orientation == PlotOrientation.HORIZONTAL) { 643 // clip left and right bounds to data area 644 bottom = Math.max(bottom, dataArea.getMinX()); 645 top = Math.min(top, dataArea.getMaxX()); 646 bar = new Rectangle2D.Double( 647 bottom, left, top - bottom, translatedWidth); 648 } 649 else if (orientation == PlotOrientation.VERTICAL) { 650 // clip top and bottom bounds to data area 651 bottom = Math.max(bottom, dataArea.getMinY()); 652 top = Math.min(top, dataArea.getMaxY()); 653 bar = new Rectangle2D.Double(left, bottom, translatedWidth, 654 top - bottom); 655 } 656 657 Paint itemPaint = getItemPaint(series, item); 658 if (getGradientPaintTransformer() 659 != null && itemPaint instanceof GradientPaint) { 660 GradientPaint gp = (GradientPaint) itemPaint; 661 itemPaint = getGradientPaintTransformer().transform(gp, bar); 662 } 663 g2.setPaint(itemPaint); 664 g2.fill(bar); 665 if (isDrawBarOutline() 666 && Math.abs(translatedEndX - translatedStartX) > 3) { 667 Stroke stroke = getItemOutlineStroke(series, item); 668 Paint paint = getItemOutlinePaint(series, item); 669 if (stroke != null && paint != null) { 670 g2.setStroke(stroke); 671 g2.setPaint(paint); 672 g2.draw(bar); 673 } 674 } 675 676 if (isItemLabelVisible(series, item)) { 677 XYItemLabelGenerator generator = getItemLabelGenerator(series, 678 item); 679 drawItemLabel(g2, dataset, series, item, plot, generator, bar, 680 value1 < 0.0); 681 } 682 683 // update the crosshair point 684 double x1 = (startX + endX) / 2.0; 685 double y1 = dataset.getYValue(series, item); 686 double transX1 = domainAxis.valueToJava2D(x1, dataArea, location); 687 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 688 plot.getRangeAxisEdge()); 689 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 690 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 691 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 692 rangeAxisIndex, transX1, transY1, plot.getOrientation()); 693 694 EntityCollection entities = state.getEntityCollection(); 695 if (entities != null) { 696 addEntity(entities, bar, dataset, series, item, 0.0, 0.0); 697 } 698 699 } 700 701 /** 702 * Draws an item label. This method is provided as an alternative to 703 * {@link #drawItemLabel(Graphics2D, PlotOrientation, XYDataset, int, int, 704 * double, double, boolean)} so that the bar can be used to calculate the 705 * label anchor point. 706 * 707 * @param g2 the graphics device. 708 * @param dataset the dataset. 709 * @param series the series index. 710 * @param item the item index. 711 * @param plot the plot. 712 * @param generator the label generator (<code>null</code> permitted, in 713 * which case the method does nothing, just returns). 714 * @param bar the bar. 715 * @param negative a flag indicating a negative value. 716 */ 717 protected void drawItemLabel(Graphics2D g2, XYDataset dataset, 718 int series, int item, XYPlot plot, XYItemLabelGenerator generator, 719 Rectangle2D bar, boolean negative) { 720 721 if (generator == null) { 722 return; // nothing to do 723 } 724 String label = generator.generateLabel(dataset, series, item); 725 if (label == null) { 726 return; // nothing to do 727 } 728 729 Font labelFont = getItemLabelFont(series, item); 730 g2.setFont(labelFont); 731 Paint paint = getItemLabelPaint(series, item); 732 g2.setPaint(paint); 733 734 // find out where to place the label... 735 ItemLabelPosition position = null; 736 if (!negative) { 737 position = getPositiveItemLabelPosition(series, item); 738 } 739 else { 740 position = getNegativeItemLabelPosition(series, item); 741 } 742 743 // work out the label anchor point... 744 Point2D anchorPoint = calculateLabelAnchorPoint( 745 position.getItemLabelAnchor(), bar, plot.getOrientation()); 746 747 if (isInternalAnchor(position.getItemLabelAnchor())) { 748 Shape bounds = TextUtilities.calculateRotatedStringBounds(label, 749 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), 750 position.getTextAnchor(), position.getAngle(), 751 position.getRotationAnchor()); 752 753 if (bounds != null) { 754 if (!bar.contains(bounds.getBounds2D())) { 755 if (!negative) { 756 position = getPositiveItemLabelPositionFallback(); 757 } 758 else { 759 position = getNegativeItemLabelPositionFallback(); 760 } 761 if (position != null) { 762 anchorPoint = calculateLabelAnchorPoint( 763 position.getItemLabelAnchor(), bar, 764 plot.getOrientation()); 765 } 766 } 767 } 768 769 } 770 771 if (position != null) { 772 TextUtilities.drawRotatedString(label, g2, 773 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 774 position.getTextAnchor(), position.getAngle(), 775 position.getRotationAnchor()); 776 } 777 } 778 779 /** 780 * Calculates the item label anchor point. 781 * 782 * @param anchor the anchor. 783 * @param bar the bar. 784 * @param orientation the plot orientation. 785 * 786 * @return The anchor point. 787 */ 788 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor, 789 Rectangle2D bar, PlotOrientation orientation) { 790 791 Point2D result = null; 792 double offset = getItemLabelAnchorOffset(); 793 double x0 = bar.getX() - offset; 794 double x1 = bar.getX(); 795 double x2 = bar.getX() + offset; 796 double x3 = bar.getCenterX(); 797 double x4 = bar.getMaxX() - offset; 798 double x5 = bar.getMaxX(); 799 double x6 = bar.getMaxX() + offset; 800 801 double y0 = bar.getMaxY() + offset; 802 double y1 = bar.getMaxY(); 803 double y2 = bar.getMaxY() - offset; 804 double y3 = bar.getCenterY(); 805 double y4 = bar.getMinY() + offset; 806 double y5 = bar.getMinY(); 807 double y6 = bar.getMinY() - offset; 808 809 if (anchor == ItemLabelAnchor.CENTER) { 810 result = new Point2D.Double(x3, y3); 811 } 812 else if (anchor == ItemLabelAnchor.INSIDE1) { 813 result = new Point2D.Double(x4, y4); 814 } 815 else if (anchor == ItemLabelAnchor.INSIDE2) { 816 result = new Point2D.Double(x4, y4); 817 } 818 else if (anchor == ItemLabelAnchor.INSIDE3) { 819 result = new Point2D.Double(x4, y3); 820 } 821 else if (anchor == ItemLabelAnchor.INSIDE4) { 822 result = new Point2D.Double(x4, y2); 823 } 824 else if (anchor == ItemLabelAnchor.INSIDE5) { 825 result = new Point2D.Double(x4, y2); 826 } 827 else if (anchor == ItemLabelAnchor.INSIDE6) { 828 result = new Point2D.Double(x3, y2); 829 } 830 else if (anchor == ItemLabelAnchor.INSIDE7) { 831 result = new Point2D.Double(x2, y2); 832 } 833 else if (anchor == ItemLabelAnchor.INSIDE8) { 834 result = new Point2D.Double(x2, y2); 835 } 836 else if (anchor == ItemLabelAnchor.INSIDE9) { 837 result = new Point2D.Double(x2, y3); 838 } 839 else if (anchor == ItemLabelAnchor.INSIDE10) { 840 result = new Point2D.Double(x2, y4); 841 } 842 else if (anchor == ItemLabelAnchor.INSIDE11) { 843 result = new Point2D.Double(x2, y4); 844 } 845 else if (anchor == ItemLabelAnchor.INSIDE12) { 846 result = new Point2D.Double(x3, y4); 847 } 848 else if (anchor == ItemLabelAnchor.OUTSIDE1) { 849 result = new Point2D.Double(x5, y6); 850 } 851 else if (anchor == ItemLabelAnchor.OUTSIDE2) { 852 result = new Point2D.Double(x6, y5); 853 } 854 else if (anchor == ItemLabelAnchor.OUTSIDE3) { 855 result = new Point2D.Double(x6, y3); 856 } 857 else if (anchor == ItemLabelAnchor.OUTSIDE4) { 858 result = new Point2D.Double(x6, y1); 859 } 860 else if (anchor == ItemLabelAnchor.OUTSIDE5) { 861 result = new Point2D.Double(x5, y0); 862 } 863 else if (anchor == ItemLabelAnchor.OUTSIDE6) { 864 result = new Point2D.Double(x3, y0); 865 } 866 else if (anchor == ItemLabelAnchor.OUTSIDE7) { 867 result = new Point2D.Double(x1, y0); 868 } 869 else if (anchor == ItemLabelAnchor.OUTSIDE8) { 870 result = new Point2D.Double(x0, y1); 871 } 872 else if (anchor == ItemLabelAnchor.OUTSIDE9) { 873 result = new Point2D.Double(x0, y3); 874 } 875 else if (anchor == ItemLabelAnchor.OUTSIDE10) { 876 result = new Point2D.Double(x0, y5); 877 } 878 else if (anchor == ItemLabelAnchor.OUTSIDE11) { 879 result = new Point2D.Double(x1, y6); 880 } 881 else if (anchor == ItemLabelAnchor.OUTSIDE12) { 882 result = new Point2D.Double(x3, y6); 883 } 884 885 return result; 886 887 } 888 889 /** 890 * Returns <code>true</code> if the specified anchor point is inside a bar. 891 * 892 * @param anchor the anchor point. 893 * 894 * @return A boolean. 895 */ 896 private boolean isInternalAnchor(ItemLabelAnchor anchor) { 897 return anchor == ItemLabelAnchor.CENTER 898 || anchor == ItemLabelAnchor.INSIDE1 899 || anchor == ItemLabelAnchor.INSIDE2 900 || anchor == ItemLabelAnchor.INSIDE3 901 || anchor == ItemLabelAnchor.INSIDE4 902 || anchor == ItemLabelAnchor.INSIDE5 903 || anchor == ItemLabelAnchor.INSIDE6 904 || anchor == ItemLabelAnchor.INSIDE7 905 || anchor == ItemLabelAnchor.INSIDE8 906 || anchor == ItemLabelAnchor.INSIDE9 907 || anchor == ItemLabelAnchor.INSIDE10 908 || anchor == ItemLabelAnchor.INSIDE11 909 || anchor == ItemLabelAnchor.INSIDE12; 910 } 911 912 /** 913 * Returns the lower and upper bounds (range) of the x-values in the 914 * specified dataset. Since this renderer uses the x-interval in the 915 * dataset, this is taken into account for the range. 916 * 917 * @param dataset the dataset (<code>null</code> permitted). 918 * 919 * @return The range (<code>null</code> if the dataset is 920 * <code>null</code> or empty). 921 */ 922 public Range findDomainBounds(XYDataset dataset) { 923 if (dataset != null) { 924 return DatasetUtilities.findDomainBounds(dataset, true); 925 } 926 else { 927 return null; 928 } 929 } 930 931 /** 932 * Returns a clone of the renderer. 933 * 934 * @return A clone. 935 * 936 * @throws CloneNotSupportedException if the renderer cannot be cloned. 937 */ 938 public Object clone() throws CloneNotSupportedException { 939 XYBarRenderer result = (XYBarRenderer) super.clone(); 940 if (this.gradientPaintTransformer != null) { 941 result.gradientPaintTransformer = (GradientPaintTransformer) 942 ObjectUtilities.clone(this.gradientPaintTransformer); 943 } 944 result.legendBar = ShapeUtilities.clone(this.legendBar); 945 return result; 946 } 947 948 /** 949 * Tests this renderer for equality with an arbitrary object. 950 * 951 * @param obj the object to test against (<code>null</code> permitted). 952 * 953 * @return A boolean. 954 */ 955 public boolean equals(Object obj) { 956 if (obj == this) { 957 return true; 958 } 959 if (!(obj instanceof XYBarRenderer)) { 960 return false; 961 } 962 if (!super.equals(obj)) { 963 return false; 964 } 965 XYBarRenderer that = (XYBarRenderer) obj; 966 if (this.base != that.base) { 967 return false; 968 } 969 if (this.drawBarOutline != that.drawBarOutline) { 970 return false; 971 } 972 if (this.margin != that.margin) { 973 return false; 974 } 975 if (this.useYInterval != that.useYInterval) { 976 return false; 977 } 978 if (!ObjectUtilities.equal( 979 this.gradientPaintTransformer, that.gradientPaintTransformer) 980 ) { 981 return false; 982 } 983 if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) { 984 return false; 985 } 986 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback, 987 that.positiveItemLabelPositionFallback)) { 988 return false; 989 } 990 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback, 991 that.negativeItemLabelPositionFallback)) { 992 return false; 993 } 994 return true; 995 } 996 997 /** 998 * Provides serialization support. 999 * 1000 * @param stream the input stream. 1001 * 1002 * @throws IOException if there is an I/O error. 1003 * @throws ClassNotFoundException if there is a classpath problem. 1004 */ 1005 private void readObject(ObjectInputStream stream) 1006 throws IOException, ClassNotFoundException { 1007 stream.defaultReadObject(); 1008 this.legendBar = SerialUtilities.readShape(stream); 1009 } 1010 1011 /** 1012 * Provides serialization support. 1013 * 1014 * @param stream the output stream. 1015 * 1016 * @throws IOException if there is an I/O error. 1017 */ 1018 private void writeObject(ObjectOutputStream stream) throws IOException { 1019 stream.defaultWriteObject(); 1020 SerialUtilities.writeShape(this.legendBar, stream); 1021 } 1022 1023 }