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