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 * <P> 091 * This renderer does not include code to calculate the crosshair point for the 092 * plot. 093 */ 094 public class ClusteredXYBarRenderer extends XYBarRenderer 095 implements Cloneable, PublicCloneable, Serializable { 096 097 /** For serialization. */ 098 private static final long serialVersionUID = 5864462149177133147L; 099 100 /** Determines whether bar center should be interval start. */ 101 private boolean centerBarAtStartValue; 102 103 /** 104 * Default constructor. Bar margin is set to 0.0. 105 */ 106 public ClusteredXYBarRenderer() { 107 this(0.0, false); 108 } 109 110 /** 111 * Constructs a new XY clustered bar renderer. 112 * 113 * @param margin the percentage amount to trim from the width of each bar. 114 * @param centerBarAtStartValue if true, bars will be centered on the 115 * start of the time period. 116 */ 117 public ClusteredXYBarRenderer(double margin, 118 boolean centerBarAtStartValue) { 119 super(margin); 120 this.centerBarAtStartValue = centerBarAtStartValue; 121 } 122 123 /** 124 * Returns the number of passes through the dataset that this renderer 125 * requires. In this case, two passes are required, the first for drawing 126 * the shadows (if visible), and the second for drawing the bars. 127 * 128 * @return <code>2</code>. 129 */ 130 public int getPassCount() { 131 return 2; 132 } 133 134 /** 135 * Returns the x-value bounds for the specified dataset. 136 * 137 * @param dataset the dataset (<code>null</code> permitted). 138 * 139 * @return The bounds (possibly <code>null</code>). 140 */ 141 public Range findDomainBounds(XYDataset dataset) { 142 if (dataset == null) { 143 return null; 144 } 145 // need to handle cluster centering as a special case 146 if (this.centerBarAtStartValue) { 147 return findDomainBoundsWithOffset((IntervalXYDataset) dataset); 148 } 149 else { 150 return super.findDomainBounds(dataset); 151 } 152 } 153 154 /** 155 * Iterates over the items in an {@link IntervalXYDataset} to find 156 * the range of x-values including the interval OFFSET so that it centers 157 * the interval around the start value. 158 * 159 * @param dataset the dataset (<code>null</code> not permitted). 160 * 161 * @return The range (possibly <code>null</code>). 162 */ 163 protected Range findDomainBoundsWithOffset(IntervalXYDataset dataset) { 164 if (dataset == null) { 165 throw new IllegalArgumentException("Null 'dataset' argument."); 166 } 167 double minimum = Double.POSITIVE_INFINITY; 168 double maximum = Double.NEGATIVE_INFINITY; 169 int seriesCount = dataset.getSeriesCount(); 170 double lvalue; 171 double uvalue; 172 for (int series = 0; series < seriesCount; series++) { 173 int itemCount = dataset.getItemCount(series); 174 for (int item = 0; item < itemCount; item++) { 175 lvalue = dataset.getStartXValue(series, item); 176 uvalue = dataset.getEndXValue(series, item); 177 double offset = (uvalue - lvalue) / 2.0; 178 lvalue = lvalue - offset; 179 uvalue = uvalue - offset; 180 minimum = Math.min(minimum, lvalue); 181 maximum = Math.max(maximum, uvalue); 182 } 183 } 184 185 if (minimum > maximum) { 186 return null; 187 } 188 else { 189 return new Range(minimum, maximum); 190 } 191 } 192 193 /** 194 * Draws the visual representation of a single data item. This method 195 * is mostly copied from the superclass, the change is that in the 196 * calculated space for a singe bar we draw bars for each series next to 197 * each other. The width of each bar is the available width divided by 198 * the number of series. Bars for each series are drawn in order left to 199 * right. 200 * 201 * @param g2 the graphics device. 202 * @param state the renderer state. 203 * @param dataArea the area within which the plot is being drawn. 204 * @param info collects information about the drawing. 205 * @param plot the plot (can be used to obtain standard color 206 * information etc). 207 * @param domainAxis the domain axis. 208 * @param rangeAxis the range axis. 209 * @param dataset the dataset. 210 * @param series the series index. 211 * @param item the item index. 212 * @param crosshairState crosshair information for the plot 213 * (<code>null</code> permitted). 214 * @param pass the pass index. 215 */ 216 public void drawItem(Graphics2D g2, 217 XYItemRendererState state, 218 Rectangle2D dataArea, 219 PlotRenderingInfo info, 220 XYPlot plot, 221 ValueAxis domainAxis, 222 ValueAxis rangeAxis, 223 XYDataset dataset, int series, int item, 224 CrosshairState crosshairState, 225 int pass) { 226 227 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 228 229 double y0; 230 double y1; 231 if (getUseYInterval()) { 232 y0 = intervalDataset.getStartYValue(series, item); 233 y1 = intervalDataset.getEndYValue(series, item); 234 } 235 else { 236 y0 = getBase(); 237 y1 = intervalDataset.getYValue(series, item); 238 } 239 if (Double.isNaN(y0) || Double.isNaN(y1)) { 240 return; 241 } 242 243 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, 244 plot.getRangeAxisEdge()); 245 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, 246 plot.getRangeAxisEdge()); 247 248 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 249 double x0 = intervalDataset.getStartXValue(series, item); 250 double xx0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 251 252 double x1 = intervalDataset.getEndXValue(series, item); 253 double xx1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 254 255 double intervalW = xx1 - xx0; // this may be negative 256 double baseX = xx0; 257 if (this.centerBarAtStartValue) { 258 baseX = baseX - intervalW / 2.0; 259 } 260 double m = getMargin(); 261 if (m > 0.0) { 262 double cut = intervalW * getMargin(); 263 intervalW = intervalW - cut; 264 baseX = baseX + (cut / 2); 265 } 266 267 double intervalH = Math.abs(yy0 - yy1); // we don't need the sign 268 269 PlotOrientation orientation = plot.getOrientation(); 270 271 int numSeries = dataset.getSeriesCount(); 272 double seriesBarWidth = intervalW / numSeries; // may be negative 273 274 Rectangle2D bar = null; 275 if (orientation == PlotOrientation.HORIZONTAL) { 276 double barY0 = baseX + (seriesBarWidth * series); 277 double barY1 = barY0 + seriesBarWidth; 278 double rx = Math.min(yy0, yy1); 279 double rw = intervalH; 280 double ry = Math.min(barY0, barY1); 281 double rh = Math.abs(barY1 - barY0); 282 bar = new Rectangle2D.Double(rx, ry, rw, rh); 283 } 284 else if (orientation == PlotOrientation.VERTICAL) { 285 double barX0 = baseX + (seriesBarWidth * series); 286 double barX1 = barX0 + seriesBarWidth; 287 double rx = Math.min(barX0, barX1); 288 double rw = Math.abs(barX1 - barX0); 289 double ry = Math.min(yy0, yy1); 290 double rh = intervalH; 291 bar = new Rectangle2D.Double(rx, ry, rw, rh); 292 } 293 boolean positive = (y1 > 0.0); 294 boolean inverted = rangeAxis.isInverted(); 295 RectangleEdge barBase; 296 if (orientation == PlotOrientation.HORIZONTAL) { 297 if (positive && inverted || !positive && !inverted) { 298 barBase = RectangleEdge.RIGHT; 299 } 300 else { 301 barBase = RectangleEdge.LEFT; 302 } 303 } 304 else { 305 if (positive && !inverted || !positive && inverted) { 306 barBase = RectangleEdge.BOTTOM; 307 } 308 else { 309 barBase = RectangleEdge.TOP; 310 } 311 } 312 if (pass == 0 && getShadowsVisible()) { 313 getBarPainter().paintBarShadow(g2, this, series, item, bar, barBase, 314 !getUseYInterval()); 315 } 316 if (pass == 1) { 317 getBarPainter().paintBar(g2, this, series, item, bar, barBase); 318 319 if (isItemLabelVisible(series, item)) { 320 XYItemLabelGenerator generator = getItemLabelGenerator(series, 321 item); 322 drawItemLabel(g2, dataset, series, item, plot, generator, bar, 323 y1 < 0.0); 324 } 325 326 // add an entity for the item... 327 if (info != null) { 328 EntityCollection entities 329 = info.getOwner().getEntityCollection(); 330 if (entities != null) { 331 addEntity(entities, bar, dataset, series, item, 332 bar.getCenterX(), bar.getCenterY()); 333 } 334 } 335 } 336 337 } 338 339 /** 340 * Tests this renderer for equality with an arbitrary object, returning 341 * <code>true</code> if <code>obj</code> is a 342 * <code>ClusteredXYBarRenderer</code> with the same settings as this 343 * renderer, and <code>false</code> otherwise. 344 * 345 * @param obj the object (<code>null</code> permitted). 346 * 347 * @return A boolean. 348 */ 349 public boolean equals(Object obj) { 350 if (obj == this) { 351 return true; 352 } 353 if (!(obj instanceof ClusteredXYBarRenderer)) { 354 return false; 355 } 356 ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj; 357 if (this.centerBarAtStartValue != that.centerBarAtStartValue) { 358 return false; 359 } 360 return super.equals(obj); 361 } 362 363 /** 364 * Returns a clone of the renderer. 365 * 366 * @return A clone. 367 * 368 * @throws CloneNotSupportedException if the renderer cannot be cloned. 369 */ 370 public Object clone() throws CloneNotSupportedException { 371 return super.clone(); 372 } 373 374 }