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 * StackedXYAreaRenderer2.java 029 * --------------------------- 030 * (C) Copyright 2004, 2005, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited), based on 033 * the StackedXYAreaRenderer class by Richard Atkinson; 034 * Contributor(s): -; 035 * 036 * $Id: StackedXYAreaRenderer2.java,v 1.6.2.2 2005/10/25 20:56:21 mungady Exp $ 037 * 038 * Changes: 039 * -------- 040 * 30-Apr-2004 : Version 1 (DG); 041 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 042 * getYValue() (DG); 043 * 10-Sep-2004 : Removed getRangeType() method (DG); 044 * 06-Jan-2004 : Renamed getRangeExtent() --> findRangeBounds (DG); 045 * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG); 046 * 03-Oct-2005 : Add entity generation to drawItem() method (DG); 047 * 048 */ 049 050 package org.jfree.chart.renderer.xy; 051 052 import java.awt.Graphics2D; 053 import java.awt.Paint; 054 import java.awt.Shape; 055 import java.awt.geom.GeneralPath; 056 import java.awt.geom.Rectangle2D; 057 import java.io.Serializable; 058 059 import org.jfree.chart.axis.ValueAxis; 060 import org.jfree.chart.entity.EntityCollection; 061 import org.jfree.chart.labels.XYToolTipGenerator; 062 import org.jfree.chart.plot.CrosshairState; 063 import org.jfree.chart.plot.PlotRenderingInfo; 064 import org.jfree.chart.plot.XYPlot; 065 import org.jfree.chart.urls.XYURLGenerator; 066 import org.jfree.data.Range; 067 import org.jfree.data.xy.TableXYDataset; 068 import org.jfree.data.xy.XYDataset; 069 import org.jfree.ui.RectangleEdge; 070 import org.jfree.util.PublicCloneable; 071 072 /** 073 * A stacked area renderer for the {@link XYPlot} class. 074 */ 075 public class StackedXYAreaRenderer2 extends XYAreaRenderer2 076 implements Cloneable, 077 PublicCloneable, 078 Serializable { 079 080 /** For serialization. */ 081 private static final long serialVersionUID = 7752676509764539182L; 082 083 /** 084 * Creates a new renderer. 085 */ 086 public StackedXYAreaRenderer2() { 087 this(null, null); 088 } 089 090 /** 091 * Constructs a new renderer. 092 * 093 * @param labelGenerator the tool tip generator to use. <code>null</code> 094 * is none. 095 * @param urlGenerator the URL generator (<code>null</code> permitted). 096 */ 097 public StackedXYAreaRenderer2(XYToolTipGenerator labelGenerator, 098 XYURLGenerator urlGenerator) { 099 super(labelGenerator, urlGenerator); 100 } 101 102 /** 103 * Returns the range of values the renderer requires to display all the 104 * items from the specified dataset. 105 * 106 * @param dataset the dataset (<code>null</code> permitted). 107 * 108 * @return The range (or <code>null</code> if the dataset is 109 * <code>null</code> or empty). 110 */ 111 public Range findRangeBounds(XYDataset dataset) { 112 double min = Double.POSITIVE_INFINITY; 113 double max = Double.NEGATIVE_INFINITY; 114 TableXYDataset d = (TableXYDataset) dataset; 115 int itemCount = d.getItemCount(); 116 for (int i = 0; i < itemCount; i++) { 117 double[] stackValues = getStackValues( 118 (TableXYDataset) dataset, d.getSeriesCount(), i 119 ); 120 min = Math.min(min, stackValues[0]); 121 max = Math.max(max, stackValues[1]); 122 } 123 return new Range(min, max); 124 } 125 126 /** 127 * Returns the number of passes required by the renderer. 128 * 129 * @return 1. 130 */ 131 public int getPassCount() { 132 return 1; 133 } 134 135 /** 136 * Draws the visual representation of a single data item. 137 * 138 * @param g2 the graphics device. 139 * @param state the renderer state. 140 * @param dataArea the area within which the data is being drawn. 141 * @param info collects information about the drawing. 142 * @param plot the plot (can be used to obtain standard color information 143 * etc). 144 * @param domainAxis the domain axis. 145 * @param rangeAxis the range axis. 146 * @param dataset the dataset. 147 * @param series the series index (zero-based). 148 * @param item the item index (zero-based). 149 * @param crosshairState information about crosshairs on a plot. 150 * @param pass the pass index. 151 */ 152 public void drawItem(Graphics2D g2, 153 XYItemRendererState state, 154 Rectangle2D dataArea, 155 PlotRenderingInfo info, 156 XYPlot plot, 157 ValueAxis domainAxis, 158 ValueAxis rangeAxis, 159 XYDataset dataset, 160 int series, 161 int item, 162 CrosshairState crosshairState, 163 int pass) { 164 165 // setup for collecting optional entity info... 166 Shape entityArea = null; 167 EntityCollection entities = null; 168 if (info != null) { 169 entities = info.getOwner().getEntityCollection(); 170 } 171 172 TableXYDataset tdataset = (TableXYDataset) dataset; 173 174 // get the data point... 175 double x1 = dataset.getXValue(series, item); 176 double y1 = dataset.getYValue(series, item); 177 if (Double.isNaN(y1)) { 178 y1 = 0.0; 179 } 180 double[] stack1 = getStackValues(tdataset, series, item); 181 182 // get the previous point and the next point so we can calculate a 183 // "hot spot" for the area (used by the chart entity)... 184 double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); 185 double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); 186 if (Double.isNaN(y0)) { 187 y0 = 0.0; 188 } 189 double[] stack0 = getStackValues( 190 tdataset, series, Math.max(item - 1, 0) 191 ); 192 193 int itemCount = dataset.getItemCount(series); 194 double x2 = dataset.getXValue( 195 series, Math.min(item + 1, itemCount - 1) 196 ); 197 double y2 = dataset.getYValue( 198 series, Math.min(item + 1, itemCount - 1) 199 ); 200 if (Double.isNaN(y2)) { 201 y2 = 0.0; 202 } 203 double[] stack2 = getStackValues( 204 tdataset, series, Math.min(item + 1, itemCount - 1) 205 ); 206 207 double xleft = (x0 + x1) / 2.0; 208 double xright = (x1 + x2) / 2.0; 209 double[] stackLeft = averageStackValues(stack0, stack1); 210 double[] stackRight = averageStackValues(stack1, stack2); 211 double[] adjStackLeft = adjustedStackValues(stack0, stack1); 212 double[] adjStackRight = adjustedStackValues(stack1, stack2); 213 214 RectangleEdge edge0 = plot.getDomainAxisEdge(); 215 float transX1 = (float) domainAxis.valueToJava2D(x1, dataArea, edge0); 216 float transXLeft 217 = (float) domainAxis.valueToJava2D(xleft, dataArea, edge0); 218 float transXRight 219 = (float) domainAxis.valueToJava2D(xright, dataArea, edge0); 220 float transY1; 221 222 RectangleEdge edge1 = plot.getRangeAxisEdge(); 223 224 GeneralPath left = new GeneralPath(); 225 GeneralPath right = new GeneralPath(); 226 if (y1 >= 0.0) { // handle positive value 227 transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[1], dataArea, 228 edge1); 229 float transStack1 = (float) rangeAxis.valueToJava2D( 230 stack1[1], dataArea, edge1 231 ); 232 float transStackLeft = (float) rangeAxis.valueToJava2D( 233 adjStackLeft[1], dataArea, edge1 234 ); 235 236 // LEFT POLYGON 237 if (y0 >= 0.0) { 238 double yleft = (y0 + y1) / 2.0 + stackLeft[1]; 239 float transYLeft 240 = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1); 241 left.moveTo(transX1, transY1); 242 left.lineTo(transX1, transStack1); 243 left.lineTo(transXLeft, transStackLeft); 244 left.lineTo(transXLeft, transYLeft); 245 left.closePath(); 246 } 247 else { 248 left.moveTo(transX1, transStack1); 249 left.lineTo(transX1, transY1); 250 left.lineTo(transXLeft, transStackLeft); 251 left.closePath(); 252 } 253 254 float transStackRight = (float) rangeAxis.valueToJava2D( 255 adjStackRight[1], dataArea, edge1 256 ); 257 // RIGHT POLYGON 258 if (y2 >= 0.0) { 259 double yright = (y1 + y2) / 2.0 + stackRight[1]; 260 float transYRight 261 = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1); 262 right.moveTo(transX1, transStack1); 263 right.lineTo(transX1, transY1); 264 right.lineTo(transXRight, transYRight); 265 right.lineTo(transXRight, transStackRight); 266 right.closePath(); 267 } 268 else { 269 right.moveTo(transX1, transStack1); 270 right.lineTo(transX1, transY1); 271 right.lineTo(transXRight, transStackRight); 272 right.closePath(); 273 } 274 } 275 else { // handle negative value 276 transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[0], dataArea, 277 edge1); 278 float transStack1 = (float) rangeAxis.valueToJava2D( 279 stack1[0], dataArea, edge1 280 ); 281 float transStackLeft = (float) rangeAxis.valueToJava2D( 282 adjStackLeft[0], dataArea, edge1 283 ); 284 285 // LEFT POLYGON 286 if (y0 >= 0.0) { 287 left.moveTo(transX1, transStack1); 288 left.lineTo(transX1, transY1); 289 left.lineTo(transXLeft, transStackLeft); 290 left.clone(); 291 } 292 else { 293 double yleft = (y0 + y1) / 2.0 + stackLeft[0]; 294 float transYLeft = (float) rangeAxis.valueToJava2D( 295 yleft, dataArea, edge1 296 ); 297 left.moveTo(transX1, transY1); 298 left.lineTo(transX1, transStack1); 299 left.lineTo(transXLeft, transStackLeft); 300 left.lineTo(transXLeft, transYLeft); 301 left.closePath(); 302 } 303 float transStackRight = (float) rangeAxis.valueToJava2D( 304 adjStackRight[0], dataArea, edge1 305 ); 306 307 // RIGHT POLYGON 308 if (y2 >= 0.0) { 309 right.moveTo(transX1, transStack1); 310 right.lineTo(transX1, transY1); 311 right.lineTo(transXRight, transStackRight); 312 right.closePath(); 313 } 314 else { 315 double yright = (y1 + y2) / 2.0 + stackRight[0]; 316 float transYRight = (float) rangeAxis.valueToJava2D( 317 yright, dataArea, edge1 318 ); 319 right.moveTo(transX1, transStack1); 320 right.lineTo(transX1, transY1); 321 right.lineTo(transXRight, transYRight); 322 right.lineTo(transXRight, transStackRight); 323 right.closePath(); 324 } 325 } 326 327 // Get series Paint and Stroke 328 Paint itemPaint = getItemPaint(series, item); 329 if (pass == 0) { 330 g2.setPaint(itemPaint); 331 g2.fill(left); 332 g2.fill(right); 333 } 334 335 // add an entity for the item... 336 if (entities != null) { 337 GeneralPath gp = new GeneralPath(left); 338 gp.append(right, false); 339 entityArea = gp; 340 addEntity(entities, entityArea, dataset, series, item, 341 transX1, transY1); 342 } 343 344 } 345 346 /** 347 * Calculates the stacked value of the all series up to, but not including 348 * <code>series</code> for the specified item. It returns 0.0 if 349 * <code>series</code> is the first series, i.e. 0. 350 * 351 * @param dataset the dataset. 352 * @param series the series. 353 * @param index the index. 354 * 355 * @return The cumulative value for all series' values up to but excluding 356 * <code>series</code> for <code>index</code>. 357 */ 358 private double[] getStackValues(TableXYDataset dataset, 359 int series, int index) { 360 double[] result = new double[2]; 361 for (int i = 0; i < series; i++) { 362 double v = dataset.getYValue(i, index); 363 if (!Double.isNaN(v)) { 364 if (v >= 0.0) { 365 result[1] += v; 366 } 367 else { 368 result[0] += v; 369 } 370 } 371 } 372 return result; 373 } 374 375 /** 376 * Returns a pair of "stack" values calculated from the two specified pairs. 377 * 378 * @param stack1 the first stack pair. 379 * @param stack2 the second stack pair. 380 * 381 * @return A pair of average stack values. 382 */ 383 private double[] averageStackValues(double[] stack1, double[] stack2) { 384 double[] result = new double[2]; 385 result[0] = (stack1[0] + stack2[0]) / 2.0; 386 result[1] = (stack1[1] + stack2[1]) / 2.0; 387 return result; 388 } 389 390 /** 391 * Returns a pair of "stack" values calculated from the two specified pairs. 392 * 393 * @param stack1 the first stack pair. 394 * @param stack2 the second stack pair. 395 * 396 * @return A pair of average stack values. 397 */ 398 private double[] adjustedStackValues(double[] stack1, double[] stack2) { 399 double[] result = new double[2]; 400 if (stack1[0] == 0.0 || stack2[0] == 0.0) { 401 result[0] = 0.0; 402 } 403 else { 404 result[0] = (stack1[0] + stack2[0]) / 2.0; 405 } 406 if (stack1[1] == 0.0 || stack2[1] == 0.0) { 407 result[1] = 0.0; 408 } 409 else { 410 result[1] = (stack1[1] + stack2[1]) / 2.0; 411 } 412 return result; 413 } 414 415 /** 416 * Returns a clone of the renderer. 417 * 418 * @return A clone. 419 * 420 * @throws CloneNotSupportedException if the renderer cannot be cloned. 421 */ 422 public Object clone() throws CloneNotSupportedException { 423 return super.clone(); 424 } 425 426 }