001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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-2005, 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 * 037 * $Id: XYBarRenderer.java,v 1.14.2.4 2005/11/28 12:06:35 mungady Exp $ 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 * 075 */ 076 077 package org.jfree.chart.renderer.xy; 078 079 import java.awt.GradientPaint; 080 import java.awt.Graphics2D; 081 import java.awt.Paint; 082 import java.awt.Shape; 083 import java.awt.Stroke; 084 import java.awt.geom.Rectangle2D; 085 import java.io.IOException; 086 import java.io.ObjectInputStream; 087 import java.io.ObjectOutputStream; 088 import java.io.Serializable; 089 090 import org.jfree.chart.LegendItem; 091 import org.jfree.chart.axis.ValueAxis; 092 import org.jfree.chart.entity.EntityCollection; 093 import org.jfree.chart.entity.XYItemEntity; 094 import org.jfree.chart.event.RendererChangeEvent; 095 import org.jfree.chart.labels.XYSeriesLabelGenerator; 096 import org.jfree.chart.labels.XYToolTipGenerator; 097 import org.jfree.chart.plot.CrosshairState; 098 import org.jfree.chart.plot.PlotOrientation; 099 import org.jfree.chart.plot.PlotRenderingInfo; 100 import org.jfree.chart.plot.XYPlot; 101 import org.jfree.data.Range; 102 import org.jfree.data.general.DatasetUtilities; 103 import org.jfree.data.xy.IntervalXYDataset; 104 import org.jfree.data.xy.XYDataset; 105 import org.jfree.io.SerialUtilities; 106 import org.jfree.ui.GradientPaintTransformer; 107 import org.jfree.ui.RectangleEdge; 108 import org.jfree.ui.StandardGradientPaintTransformer; 109 import org.jfree.util.ObjectUtilities; 110 import org.jfree.util.PublicCloneable; 111 import org.jfree.util.ShapeUtilities; 112 113 /** 114 * A renderer that draws bars on an {@link XYPlot} (requires an 115 * {@link IntervalXYDataset}). 116 * <P> 117 * This renderer does not include any code for calculating the crosshair point. 118 */ 119 public class XYBarRenderer extends AbstractXYItemRenderer 120 implements XYItemRenderer, 121 Cloneable, 122 PublicCloneable, 123 Serializable { 124 125 /** For serialization. */ 126 private static final long serialVersionUID = 770559577251370036L; 127 128 /** 129 * The state class used by this renderer. 130 */ 131 protected class XYBarRendererState extends XYItemRendererState { 132 133 /** Base for bars against the range axis, in Java 2D space. */ 134 private double g2Base; 135 136 /** 137 * Creates a new state object. 138 * 139 * @param info the plot rendering info. 140 */ 141 public XYBarRendererState(PlotRenderingInfo info) { 142 super(info); 143 } 144 145 /** 146 * Returns the base (range) value in Java 2D space. 147 * 148 * @return The base value. 149 */ 150 public double getG2Base() { 151 return this.g2Base; 152 } 153 154 /** 155 * Sets the range axis base in Java2D space. 156 * 157 * @param value the value. 158 */ 159 public void setG2Base(double value) { 160 this.g2Base = value; 161 } 162 } 163 164 /** The default base value for the bars. */ 165 private double base; 166 167 /** 168 * A flag that controls whether the bars use the y-interval supplied by the 169 * dataset. 170 */ 171 private boolean useYInterval; 172 173 /** Percentage margin (to reduce the width of bars). */ 174 private double margin; 175 176 /** A flag that controls whether or not bar outlines are drawn. */ 177 private boolean drawBarOutline; 178 179 /** 180 * An optional class used to transform gradient paint objects to fit each 181 * bar. 182 */ 183 private GradientPaintTransformer gradientPaintTransformer; 184 185 /** 186 * The shape used to represent a bar in each legend item (this should never 187 * be <code>null</code>). 188 */ 189 private transient Shape legendBar; 190 191 /** 192 * The default constructor. 193 */ 194 public XYBarRenderer() { 195 this(0.0); 196 } 197 198 /** 199 * Constructs a new renderer. 200 * 201 * @param margin the percentage amount to trim from the width of each bar. 202 */ 203 public XYBarRenderer(double margin) { 204 super(); 205 this.margin = margin; 206 this.base = 0.0; 207 this.useYInterval = false; 208 this.gradientPaintTransformer = new StandardGradientPaintTransformer(); 209 this.drawBarOutline = true; 210 this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0); 211 } 212 213 /** 214 * Returns the base value for the bars. 215 * 216 * @return The base value for the bars. 217 */ 218 public double getBase() { 219 return this.base; 220 } 221 222 /** 223 * Sets the base value for the bars and sends a {@link RendererChangeEvent} 224 * to all registered listeners. The base value is not used if the dataset's 225 * y-interval is being used to determine the bar length. 226 * 227 * @param base the new base value. 228 */ 229 public void setBase(double base) { 230 this.base = base; 231 notifyListeners(new RendererChangeEvent(this)); 232 } 233 234 /** 235 * Returns a flag that determines whether the y-interval from the dataset is 236 * used to calculate the length of each bar. 237 * 238 * @return A boolean. 239 */ 240 public boolean getUseYInterval() { 241 return this.useYInterval; 242 } 243 244 /** 245 * Sets the flag that determines whether the y-interval from the dataset is 246 * used to calculate the length of each bar, and sends a 247 * {@link RendererChangeEvent} to all registered listeners.. 248 * 249 * @param use the flag. 250 */ 251 public void setUseYInterval(boolean use) { 252 this.useYInterval = use; 253 notifyListeners(new RendererChangeEvent(this)); 254 } 255 256 /** 257 * Returns the margin which is a percentage amount by which the bars are 258 * trimmed. 259 * 260 * @return The margin. 261 */ 262 public double getMargin() { 263 return this.margin; 264 } 265 266 /** 267 * Sets the percentage amount by which the bars are trimmed and sends a 268 * {@link RendererChangeEvent} to all registered listeners. 269 * 270 * @param margin the new margin. 271 */ 272 public void setMargin(double margin) { 273 this.margin = margin; 274 notifyListeners(new RendererChangeEvent(this)); 275 } 276 277 /** 278 * Returns a flag that controls whether or not bar outlines are drawn. 279 * 280 * @return A boolean. 281 */ 282 public boolean isDrawBarOutline() { 283 return this.drawBarOutline; 284 } 285 286 /** 287 * Sets the flag that controls whether or not bar outlines are drawn and 288 * sends a {@link RendererChangeEvent} to all registered listeners. 289 * 290 * @param draw the flag. 291 */ 292 public void setDrawBarOutline(boolean draw) { 293 this.drawBarOutline = draw; 294 notifyListeners(new RendererChangeEvent(this)); 295 } 296 297 /** 298 * Returns the gradient paint transformer (an object used to transform 299 * gradient paint objects to fit each bar. 300 * 301 * @return A transformer (<code>null</code> possible). 302 */ 303 public GradientPaintTransformer getGradientPaintTransformer() { 304 return this.gradientPaintTransformer; 305 } 306 307 /** 308 * Sets the gradient paint transformer and sends a 309 * {@link RendererChangeEvent} to all registered listeners. 310 * 311 * @param transformer the transformer (<code>null</code> permitted). 312 */ 313 public void setGradientPaintTransformer( 314 GradientPaintTransformer transformer) { 315 this.gradientPaintTransformer = transformer; 316 notifyListeners(new RendererChangeEvent(this)); 317 } 318 319 /** 320 * Returns the shape used to represent bars in each legend item. 321 * 322 * @return The shape used to represent bars in each legend item (never 323 * <code>null</code>). 324 */ 325 public Shape getLegendBar() { 326 return this.legendBar; 327 } 328 329 /** 330 * Sets the shape used to represent bars in each legend item. 331 * 332 * @param bar the bar shape (<code>null</code> not permitted). 333 */ 334 public void setLegendBar(Shape bar) { 335 if (bar == null) { 336 throw new IllegalArgumentException("Null 'bar' argument."); 337 } 338 this.legendBar = bar; 339 notifyListeners(new RendererChangeEvent(this)); 340 } 341 342 /** 343 * Initialises the renderer and returns a state object that should be 344 * passed to all subsequent calls to the drawItem() method. Here we 345 * calculate the Java2D y-coordinate for zero, since all the bars have 346 * their bases fixed at zero. 347 * 348 * @param g2 the graphics device. 349 * @param dataArea the area inside the axes. 350 * @param plot the plot. 351 * @param dataset the data. 352 * @param info an optional info collection object to return data back to 353 * the caller. 354 * 355 * @return A state object. 356 */ 357 public XYItemRendererState initialise(Graphics2D g2, 358 Rectangle2D dataArea, 359 XYPlot plot, 360 XYDataset dataset, 361 PlotRenderingInfo info) { 362 363 XYBarRendererState state = new XYBarRendererState(info); 364 ValueAxis rangeAxis 365 = plot.getRangeAxisForDataset(plot.indexOf(dataset)); 366 state.setG2Base( 367 rangeAxis.valueToJava2D( 368 this.base, dataArea, plot.getRangeAxisEdge() 369 ) 370 ); 371 return state; 372 373 } 374 375 /** 376 * Returns a default legend item for the specified series. Subclasses 377 * should override this method to generate customised items. 378 * 379 * @param datasetIndex the dataset index (zero-based). 380 * @param series the series index (zero-based). 381 * 382 * @return A legend item for the series. 383 */ 384 public LegendItem getLegendItem(int datasetIndex, int series) { 385 LegendItem result = null; 386 XYPlot xyplot = getPlot(); 387 if (xyplot != null) { 388 XYDataset dataset = xyplot.getDataset(datasetIndex); 389 if (dataset != null) { 390 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); 391 String label = lg.generateLabel(dataset, series); 392 String description = label; 393 String toolTipText = null; 394 if (getLegendItemToolTipGenerator() != null) { 395 toolTipText = getLegendItemToolTipGenerator().generateLabel( 396 dataset, series 397 ); 398 } 399 String urlText = null; 400 if (getLegendItemURLGenerator() != null) { 401 urlText = getLegendItemURLGenerator().generateLabel( 402 dataset, series 403 ); 404 } 405 Shape shape = this.legendBar; 406 Paint paint = getSeriesPaint(series); 407 Paint outlinePaint = getSeriesOutlinePaint(series); 408 Stroke outlineStroke = getSeriesOutlineStroke(series); 409 result = new LegendItem(label, description, toolTipText, 410 urlText, shape, paint, outlineStroke, outlinePaint); 411 } 412 } 413 return result; 414 } 415 416 /** 417 * Draws the visual representation of a single data item. 418 * 419 * @param g2 the graphics device. 420 * @param state the renderer state. 421 * @param dataArea the area within which the plot is being drawn. 422 * @param info collects information about the drawing. 423 * @param plot the plot (can be used to obtain standard color 424 * information etc). 425 * @param domainAxis the domain axis. 426 * @param rangeAxis the range axis. 427 * @param dataset the dataset. 428 * @param series the series index (zero-based). 429 * @param item the item index (zero-based). 430 * @param crosshairState crosshair information for the plot 431 * (<code>null</code> permitted). 432 * @param pass the pass index. 433 */ 434 public void drawItem(Graphics2D g2, 435 XYItemRendererState state, 436 Rectangle2D dataArea, 437 PlotRenderingInfo info, 438 XYPlot plot, 439 ValueAxis domainAxis, 440 ValueAxis rangeAxis, 441 XYDataset dataset, 442 int series, 443 int item, 444 CrosshairState crosshairState, 445 int pass) { 446 447 if (!getItemVisible(series, item)) { 448 return; 449 } 450 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 451 452 double value0; 453 double value1; 454 if (this.useYInterval) { 455 value0 = intervalDataset.getStartYValue(series, item); 456 value1 = intervalDataset.getEndYValue(series, item); 457 } 458 else { 459 value0 = this.base; 460 value1 = intervalDataset.getYValue(series, item); 461 } 462 if (Double.isNaN(value0) || Double.isNaN(value1)) { 463 return; 464 } 465 466 double translatedValue0 = rangeAxis.valueToJava2D( 467 value0, dataArea, plot.getRangeAxisEdge() 468 ); 469 double translatedValue1 = rangeAxis.valueToJava2D( 470 value1, dataArea, plot.getRangeAxisEdge() 471 ); 472 473 RectangleEdge location = plot.getDomainAxisEdge(); 474 Number startXNumber = intervalDataset.getStartX(series, item); 475 if (startXNumber == null) { 476 return; 477 } 478 double translatedStartX = domainAxis.valueToJava2D( 479 startXNumber.doubleValue(), dataArea, location 480 ); 481 482 Number endXNumber = intervalDataset.getEndX(series, item); 483 if (endXNumber == null) { 484 return; 485 } 486 double translatedEndX = domainAxis.valueToJava2D( 487 endXNumber.doubleValue(), dataArea, location 488 ); 489 490 double translatedWidth = Math.max( 491 1, Math.abs(translatedEndX - translatedStartX) 492 ); 493 double translatedHeight = Math.abs(translatedValue1 - translatedValue0); 494 495 if (getMargin() > 0.0) { 496 double cut = translatedWidth * getMargin(); 497 translatedWidth = translatedWidth - cut; 498 translatedStartX = translatedStartX + cut / 2; 499 } 500 501 Rectangle2D bar = null; 502 PlotOrientation orientation = plot.getOrientation(); 503 if (orientation == PlotOrientation.HORIZONTAL) { 504 bar = new Rectangle2D.Double( 505 Math.min(translatedValue0, translatedValue1), 506 Math.min(translatedStartX, translatedEndX), 507 translatedHeight, translatedWidth); 508 } 509 else if (orientation == PlotOrientation.VERTICAL) { 510 bar = new Rectangle2D.Double( 511 Math.min(translatedStartX, translatedEndX), 512 Math.min(translatedValue0, translatedValue1), 513 translatedWidth, translatedHeight); 514 } 515 516 Paint itemPaint = getItemPaint(series, item); 517 if (getGradientPaintTransformer() 518 != null && itemPaint instanceof GradientPaint) { 519 GradientPaint gp = (GradientPaint) itemPaint; 520 itemPaint = getGradientPaintTransformer().transform(gp, bar); 521 } 522 g2.setPaint(itemPaint); 523 g2.fill(bar); 524 if (isDrawBarOutline() 525 && Math.abs(translatedEndX - translatedStartX) > 3) { 526 Stroke stroke = getItemOutlineStroke(series, item); 527 Paint paint = getItemOutlinePaint(series, item); 528 if (stroke != null && paint != null) { 529 g2.setStroke(stroke); 530 g2.setPaint(paint); 531 g2.draw(bar); 532 } 533 } 534 535 // TODO: we need something better for the item labels 536 if (isItemLabelVisible(series, item)) { 537 drawItemLabel( 538 g2, orientation, dataset, series, item, bar.getCenterX(), 539 bar.getY(), value1 < 0.0 540 ); 541 } 542 543 // add an entity for the item... 544 if (info != null) { 545 EntityCollection entities = info.getOwner().getEntityCollection(); 546 if (entities != null) { 547 String tip = null; 548 XYToolTipGenerator generator 549 = getToolTipGenerator(series, item); 550 if (generator != null) { 551 tip = generator.generateToolTip(dataset, series, item); 552 } 553 String url = null; 554 if (getURLGenerator() != null) { 555 url = getURLGenerator().generateURL(dataset, series, item); 556 } 557 XYItemEntity entity = new XYItemEntity( 558 bar, dataset, series, item, tip, url 559 ); 560 entities.add(entity); 561 } 562 } 563 564 } 565 566 /** 567 * Returns the lower and upper bounds (range) of the x-values in the 568 * specified dataset. Since this renderer uses the x-interval in the 569 * dataset, this is taken into account for the range. 570 * 571 * @param dataset the dataset (<code>null</code> permitted). 572 * 573 * @return The range (<code>null</code> if the dataset is 574 * <code>null</code> or empty). 575 */ 576 public Range findDomainBounds(XYDataset dataset) { 577 if (dataset != null) { 578 return DatasetUtilities.findDomainBounds(dataset, true); 579 } 580 else { 581 return null; 582 } 583 } 584 585 /** 586 * Returns a clone of the renderer. 587 * 588 * @return A clone. 589 * 590 * @throws CloneNotSupportedException if the renderer cannot be cloned. 591 */ 592 public Object clone() throws CloneNotSupportedException { 593 XYBarRenderer result = (XYBarRenderer) super.clone(); 594 if (this.gradientPaintTransformer != null) { 595 result.gradientPaintTransformer = (GradientPaintTransformer) 596 ObjectUtilities.clone(this.gradientPaintTransformer); 597 } 598 return result; 599 } 600 601 /** 602 * Tests this renderer for equality with an arbitrary object. 603 * 604 * @param obj the object to test against (<code>null</code> permitted). 605 * 606 * @return A boolean. 607 */ 608 public boolean equals(Object obj) { 609 if (obj == this) { 610 return true; 611 } 612 if (!(obj instanceof XYBarRenderer)) { 613 return false; 614 } 615 if (!super.equals(obj)) { 616 return false; 617 } 618 XYBarRenderer that = (XYBarRenderer) obj; 619 if (this.base != that.base) { 620 return false; 621 } 622 if (this.drawBarOutline != that.drawBarOutline) { 623 return false; 624 } 625 if (this.margin != that.margin) { 626 return false; 627 } 628 if (this.useYInterval != that.useYInterval) { 629 return false; 630 } 631 if (!ObjectUtilities.equal( 632 this.gradientPaintTransformer, that.gradientPaintTransformer) 633 ) { 634 return false; 635 } 636 if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) { 637 return false; 638 } 639 return true; 640 } 641 642 /** 643 * Provides serialization support. 644 * 645 * @param stream the input stream. 646 * 647 * @throws IOException if there is an I/O error. 648 * @throws ClassNotFoundException if there is a classpath problem. 649 */ 650 private void readObject(ObjectInputStream stream) 651 throws IOException, ClassNotFoundException { 652 stream.defaultReadObject(); 653 this.legendBar = SerialUtilities.readShape(stream); 654 } 655 656 /** 657 * Provides serialization support. 658 * 659 * @param stream the output stream. 660 * 661 * @throws IOException if there is an I/O error. 662 */ 663 private void writeObject(ObjectOutputStream stream) throws IOException { 664 stream.defaultWriteObject(); 665 SerialUtilities.writeShape(this.legendBar, stream); 666 } 667 668 }