001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * ClusteredXYBarRenderer.java 029 * --------------------------- 030 * (C) Copyright 2003-2008, by Paolo Cova and Contributors. 031 * 032 * Original Author: Paolo Cova; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Christian W. Zuckschwerdt; 035 * Matthias Rose; 036 * 037 * Changes 038 * ------- 039 * 24-Jan-2003 : Version 1, contributed by Paolo Cova (DG); 040 * 25-Mar-2003 : Implemented Serializable (DG); 041 * 01-May-2003 : Modified drawItem() method signature (DG); 042 * 30-Jul-2003 : Modified entity constructor (CZ); 043 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 044 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 045 * 07-Oct-2003 : Added renderer state (DG); 046 * 03-Nov-2003 : In draw method added state parameter and y==null value 047 * handling (MR); 048 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 049 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 050 * getYValue() (DG); 051 * 01-Oct-2004 : Fixed bug where 'drawBarOutline' flag is ignored (DG); 052 * 16-May-2005 : Fixed to used outline stroke for bar outlines. Removed some 053 * redundant code with the result that the renderer now respects 054 * the 'base' setting from the super-class. Added an equals() 055 * method (DG); 056 * 19-May-2005 : Added minimal item label implementation - needs improving (DG); 057 * ------------- JFREECHART 1.0.x --------------------------------------------- 058 * 11-Dec-2006 : Added support for GradientPaint (DG); 059 * 12-Jun-2007 : Added override to findDomainBounds() to handle cluster offset, 060 * fixed rendering to handle inverted axes, and simplified 061 * entity generation code (DG); 062 * 24-Jun-2008 : Added new barPainter mechanism (DG); 063 * 064 */ 065 066 package org.jfree.chart.renderer.xy; 067 068 import java.awt.Graphics2D; 069 import java.awt.geom.Rectangle2D; 070 import java.io.Serializable; 071 072 import org.jfree.chart.axis.ValueAxis; 073 import org.jfree.chart.entity.EntityCollection; 074 import org.jfree.chart.labels.XYItemLabelGenerator; 075 import org.jfree.chart.plot.CrosshairState; 076 import org.jfree.chart.plot.PlotOrientation; 077 import org.jfree.chart.plot.PlotRenderingInfo; 078 import org.jfree.chart.plot.XYPlot; 079 import org.jfree.data.Range; 080 import org.jfree.data.xy.IntervalXYDataset; 081 import org.jfree.data.xy.XYDataset; 082 import org.jfree.ui.RectangleEdge; 083 import org.jfree.util.PublicCloneable; 084 085 /** 086 * An extension of {@link XYBarRenderer} that displays bars for different 087 * series values at the same x next to each other. The assumption here is 088 * that for each x (time or else) there is a y value for each series. If 089 * this is not the case, there will be spaces between bars for a given x. 090 * The example shown here is generated by the 091 * <code>ClusteredXYBarRendererDemo1.java</code> program included in the 092 * JFreeChart demo collection: 093 * <br><br> 094 * <img src="../../../../../images/ClusteredXYBarRendererSample.png" 095 * alt="ClusteredXYBarRendererSample.png" /> 096 * <P> 097 * This renderer does not include code to calculate the crosshair point for the 098 * plot. 099 */ 100 public class ClusteredXYBarRenderer extends XYBarRenderer 101 implements Cloneable, PublicCloneable, Serializable { 102 103 /** For serialization. */ 104 private static final long serialVersionUID = 5864462149177133147L; 105 106 /** Determines whether bar center should be interval start. */ 107 private boolean centerBarAtStartValue; 108 109 /** 110 * Default constructor. Bar margin is set to 0.0. 111 */ 112 public ClusteredXYBarRenderer() { 113 this(0.0, false); 114 } 115 116 /** 117 * Constructs a new XY clustered bar renderer. 118 * 119 * @param margin the percentage amount to trim from the width of each bar. 120 * @param centerBarAtStartValue if true, bars will be centered on the 121 * start of the time period. 122 */ 123 public ClusteredXYBarRenderer(double margin, 124 boolean centerBarAtStartValue) { 125 super(margin); 126 this.centerBarAtStartValue = centerBarAtStartValue; 127 } 128 129 /** 130 * Returns the number of passes through the dataset that this renderer 131 * requires. In this case, two passes are required, the first for drawing 132 * the shadows (if visible), and the second for drawing the bars. 133 * 134 * @return <code>2</code>. 135 */ 136 public int getPassCount() { 137 return 2; 138 } 139 140 /** 141 * Returns the x-value bounds for the specified dataset. 142 * 143 * @param dataset the dataset (<code>null</code> permitted). 144 * 145 * @return The bounds (possibly <code>null</code>). 146 */ 147 public Range findDomainBounds(XYDataset dataset) { 148 if (dataset == null) { 149 return null; 150 } 151 // need to handle cluster centering as a special case 152 if (this.centerBarAtStartValue) { 153 return findDomainBoundsWithOffset((IntervalXYDataset) dataset); 154 } 155 else { 156 return super.findDomainBounds(dataset); 157 } 158 } 159 160 /** 161 * Iterates over the items in an {@link IntervalXYDataset} to find 162 * the range of x-values including the interval OFFSET so that it centers 163 * the interval around the start value. 164 * 165 * @param dataset the dataset (<code>null</code> not permitted). 166 * 167 * @return The range (possibly <code>null</code>). 168 */ 169 protected Range findDomainBoundsWithOffset(IntervalXYDataset dataset) { 170 if (dataset == null) { 171 throw new IllegalArgumentException("Null 'dataset' argument."); 172 } 173 double minimum = Double.POSITIVE_INFINITY; 174 double maximum = Double.NEGATIVE_INFINITY; 175 int seriesCount = dataset.getSeriesCount(); 176 double lvalue; 177 double uvalue; 178 for (int series = 0; series < seriesCount; series++) { 179 int itemCount = dataset.getItemCount(series); 180 for (int item = 0; item < itemCount; item++) { 181 lvalue = dataset.getStartXValue(series, item); 182 uvalue = dataset.getEndXValue(series, item); 183 double offset = (uvalue - lvalue) / 2.0; 184 lvalue = lvalue - offset; 185 uvalue = uvalue - offset; 186 minimum = Math.min(minimum, lvalue); 187 maximum = Math.max(maximum, uvalue); 188 } 189 } 190 191 if (minimum > maximum) { 192 return null; 193 } 194 else { 195 return new Range(minimum, maximum); 196 } 197 } 198 199 /** 200 * Draws the visual representation of a single data item. This method 201 * is mostly copied from the superclass, the change is that in the 202 * calculated space for a singe bar we draw bars for each series next to 203 * each other. The width of each bar is the available width divided by 204 * the number of series. Bars for each series are drawn in order left to 205 * right. 206 * 207 * @param g2 the graphics device. 208 * @param state the renderer state. 209 * @param dataArea the area within which the plot is being drawn. 210 * @param info collects information about the drawing. 211 * @param plot the plot (can be used to obtain standard color 212 * information etc). 213 * @param domainAxis the domain axis. 214 * @param rangeAxis the range axis. 215 * @param dataset the dataset. 216 * @param series the series index. 217 * @param item the item index. 218 * @param crosshairState crosshair information for the plot 219 * (<code>null</code> permitted). 220 * @param pass the pass index. 221 */ 222 public void drawItem(Graphics2D g2, 223 XYItemRendererState state, 224 Rectangle2D dataArea, 225 PlotRenderingInfo info, 226 XYPlot plot, 227 ValueAxis domainAxis, 228 ValueAxis rangeAxis, 229 XYDataset dataset, int series, int item, 230 CrosshairState crosshairState, 231 int pass) { 232 233 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 234 235 double y0; 236 double y1; 237 if (getUseYInterval()) { 238 y0 = intervalDataset.getStartYValue(series, item); 239 y1 = intervalDataset.getEndYValue(series, item); 240 } 241 else { 242 y0 = getBase(); 243 y1 = intervalDataset.getYValue(series, item); 244 } 245 if (Double.isNaN(y0) || Double.isNaN(y1)) { 246 return; 247 } 248 249 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, 250 plot.getRangeAxisEdge()); 251 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, 252 plot.getRangeAxisEdge()); 253 254 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 255 double x0 = intervalDataset.getStartXValue(series, item); 256 double xx0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 257 258 double x1 = intervalDataset.getEndXValue(series, item); 259 double xx1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 260 261 double intervalW = xx1 - xx0; // this may be negative 262 double baseX = xx0; 263 if (this.centerBarAtStartValue) { 264 baseX = baseX - intervalW / 2.0; 265 } 266 double m = getMargin(); 267 if (m > 0.0) { 268 double cut = intervalW * getMargin(); 269 intervalW = intervalW - cut; 270 baseX = baseX + (cut / 2); 271 } 272 273 double intervalH = Math.abs(yy0 - yy1); // we don't need the sign 274 275 PlotOrientation orientation = plot.getOrientation(); 276 277 int numSeries = dataset.getSeriesCount(); 278 double seriesBarWidth = intervalW / numSeries; // may be negative 279 280 Rectangle2D bar = null; 281 if (orientation == PlotOrientation.HORIZONTAL) { 282 double barY0 = baseX + (seriesBarWidth * series); 283 double barY1 = barY0 + seriesBarWidth; 284 double rx = Math.min(yy0, yy1); 285 double rw = intervalH; 286 double ry = Math.min(barY0, barY1); 287 double rh = Math.abs(barY1 - barY0); 288 bar = new Rectangle2D.Double(rx, ry, rw, rh); 289 } 290 else if (orientation == PlotOrientation.VERTICAL) { 291 double barX0 = baseX + (seriesBarWidth * series); 292 double barX1 = barX0 + seriesBarWidth; 293 double rx = Math.min(barX0, barX1); 294 double rw = Math.abs(barX1 - barX0); 295 double ry = Math.min(yy0, yy1); 296 double rh = intervalH; 297 bar = new Rectangle2D.Double(rx, ry, rw, rh); 298 } 299 boolean positive = (y1 > 0.0); 300 boolean inverted = rangeAxis.isInverted(); 301 RectangleEdge barBase; 302 if (orientation == PlotOrientation.HORIZONTAL) { 303 if (positive && inverted || !positive && !inverted) { 304 barBase = RectangleEdge.RIGHT; 305 } 306 else { 307 barBase = RectangleEdge.LEFT; 308 } 309 } 310 else { 311 if (positive && !inverted || !positive && inverted) { 312 barBase = RectangleEdge.BOTTOM; 313 } 314 else { 315 barBase = RectangleEdge.TOP; 316 } 317 } 318 if (pass == 0 && getShadowsVisible()) { 319 getBarPainter().paintBarShadow(g2, this, series, item, bar, barBase, 320 !getUseYInterval()); 321 } 322 if (pass == 1) { 323 getBarPainter().paintBar(g2, this, series, item, bar, barBase); 324 325 if (isItemLabelVisible(series, item)) { 326 XYItemLabelGenerator generator = getItemLabelGenerator(series, 327 item); 328 drawItemLabel(g2, dataset, series, item, plot, generator, bar, 329 y1 < 0.0); 330 } 331 332 // add an entity for the item... 333 if (info != null) { 334 EntityCollection entities 335 = info.getOwner().getEntityCollection(); 336 if (entities != null) { 337 addEntity(entities, bar, dataset, series, item, 338 bar.getCenterX(), bar.getCenterY()); 339 } 340 } 341 } 342 343 } 344 345 /** 346 * Tests this renderer for equality with an arbitrary object, returning 347 * <code>true</code> if <code>obj</code> is a 348 * <code>ClusteredXYBarRenderer</code> with the same settings as this 349 * renderer, and <code>false</code> otherwise. 350 * 351 * @param obj the object (<code>null</code> permitted). 352 * 353 * @return A boolean. 354 */ 355 public boolean equals(Object obj) { 356 if (obj == this) { 357 return true; 358 } 359 if (!(obj instanceof ClusteredXYBarRenderer)) { 360 return false; 361 } 362 ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj; 363 if (this.centerBarAtStartValue != that.centerBarAtStartValue) { 364 return false; 365 } 366 return super.equals(obj); 367 } 368 369 /** 370 * Returns a clone of the renderer. 371 * 372 * @return A clone. 373 * 374 * @throws CloneNotSupportedException if the renderer cannot be cloned. 375 */ 376 public Object clone() throws CloneNotSupportedException { 377 return super.clone(); 378 } 379 380 }