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