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 * StackedXYAreaRenderer2.java 029 * --------------------------- 030 * (C) Copyright 2004-2014, 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 * Changes: 037 * -------- 038 * 30-Apr-2004 : Version 1 (DG); 039 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 040 * getYValue() (DG); 041 * 10-Sep-2004 : Removed getRangeType() method (DG); 042 * 06-Jan-2004 : Renamed getRangeExtent() --> findRangeBounds (DG); 043 * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG); 044 * 03-Oct-2005 : Add entity generation to drawItem() method (DG); 045 * ------------- JFREECHART 1.0.x --------------------------------------------- 046 * 22-Aug-2006 : Handle null and empty datasets correctly in the 047 * findRangeBounds() method (DG); 048 * 22-Sep-2006 : Added a flag to allow rounding of x-coordinates (after 049 * translation to Java2D space) in order to avoid the striping 050 * that can result from anti-aliasing (thanks to Doug 051 * Clayton) (DG); 052 * 30-Nov-2006 : Added accessor methods for the roundXCoordinates flag (DG); 053 * 02-Jun-2008 : Fixed bug with PlotOrientation.HORIZONTAL (DG); 054 * 055 */ 056 057package org.jfree.chart.renderer.xy; 058 059import java.awt.Graphics2D; 060import java.awt.Paint; 061import java.awt.Shape; 062import java.awt.geom.GeneralPath; 063import java.awt.geom.Rectangle2D; 064import java.io.Serializable; 065 066import org.jfree.chart.axis.ValueAxis; 067import org.jfree.chart.entity.EntityCollection; 068import org.jfree.chart.event.RendererChangeEvent; 069import org.jfree.chart.labels.XYToolTipGenerator; 070import org.jfree.chart.plot.CrosshairState; 071import org.jfree.chart.plot.PlotOrientation; 072import org.jfree.chart.plot.PlotRenderingInfo; 073import org.jfree.chart.plot.XYPlot; 074import org.jfree.chart.urls.XYURLGenerator; 075import org.jfree.data.Range; 076import org.jfree.data.xy.TableXYDataset; 077import org.jfree.data.xy.XYDataset; 078import org.jfree.ui.RectangleEdge; 079import org.jfree.util.PublicCloneable; 080 081/** 082 * A stacked area renderer for the {@link XYPlot} class. 083 * The example shown here is generated by the 084 * <code>StackedXYAreaChartDemo2.java</code> program included in the 085 * JFreeChart demo collection: 086 * <br><br> 087 * <img src="../../../../../images/StackedXYAreaRenderer2Sample.png" 088 * alt="StackedXYAreaRenderer2Sample.png"> 089 */ 090public class StackedXYAreaRenderer2 extends XYAreaRenderer2 091 implements Cloneable, PublicCloneable, Serializable { 092 093 /** For serialization. */ 094 private static final long serialVersionUID = 7752676509764539182L; 095 096 /** 097 * This flag controls whether or not the x-coordinates (in Java2D space) 098 * are rounded to integers. When set to true, this can avoid the vertical 099 * striping that anti-aliasing can generate. However, the rounding may not 100 * be appropriate for output in high resolution formats (for example, 101 * vector graphics formats such as SVG and PDF). 102 * 103 * @since 1.0.3 104 */ 105 private boolean roundXCoordinates; 106 107 /** 108 * Creates a new renderer. 109 */ 110 public StackedXYAreaRenderer2() { 111 this(null, null); 112 } 113 114 /** 115 * Constructs a new renderer. 116 * 117 * @param labelGenerator the tool tip generator to use. <code>null</code> 118 * is none. 119 * @param urlGenerator the URL generator (<code>null</code> permitted). 120 */ 121 public StackedXYAreaRenderer2(XYToolTipGenerator labelGenerator, 122 XYURLGenerator urlGenerator) { 123 super(labelGenerator, urlGenerator); 124 this.roundXCoordinates = true; 125 } 126 127 /** 128 * Returns the flag that controls whether or not the x-coordinates (in 129 * Java2D space) are rounded to integer values. 130 * 131 * @return The flag. 132 * 133 * @since 1.0.4 134 * 135 * @see #setRoundXCoordinates(boolean) 136 */ 137 public boolean getRoundXCoordinates() { 138 return this.roundXCoordinates; 139 } 140 141 /** 142 * Sets the flag that controls whether or not the x-coordinates (in 143 * Java2D space) are rounded to integer values, and sends a 144 * {@link RendererChangeEvent} to all registered listeners. 145 * 146 * @param round the new flag value. 147 * 148 * @since 1.0.4 149 * 150 * @see #getRoundXCoordinates() 151 */ 152 public void setRoundXCoordinates(boolean round) { 153 this.roundXCoordinates = round; 154 fireChangeEvent(); 155 } 156 157 /** 158 * Returns the range of values the renderer requires to display all the 159 * items from the specified dataset. 160 * 161 * @param dataset the dataset (<code>null</code> permitted). 162 * 163 * @return The range (or <code>null</code> if the dataset is 164 * <code>null</code> or empty). 165 */ 166 @Override 167 public Range findRangeBounds(XYDataset dataset) { 168 if (dataset == null) { 169 return null; 170 } 171 double min = Double.POSITIVE_INFINITY; 172 double max = Double.NEGATIVE_INFINITY; 173 TableXYDataset d = (TableXYDataset) dataset; 174 int itemCount = d.getItemCount(); 175 for (int i = 0; i < itemCount; i++) { 176 double[] stackValues = getStackValues((TableXYDataset) dataset, 177 d.getSeriesCount(), i); 178 min = Math.min(min, stackValues[0]); 179 max = Math.max(max, stackValues[1]); 180 } 181 if (min == Double.POSITIVE_INFINITY) { 182 return null; 183 } 184 return new Range(min, max); 185 } 186 187 /** 188 * Returns the number of passes required by the renderer. 189 * 190 * @return 1. 191 */ 192 @Override 193 public int getPassCount() { 194 return 1; 195 } 196 197 /** 198 * Draws the visual representation of a single data item. 199 * 200 * @param g2 the graphics device. 201 * @param state the renderer state. 202 * @param dataArea the area within which the data is being drawn. 203 * @param info collects information about the drawing. 204 * @param plot the plot (can be used to obtain standard color information 205 * etc). 206 * @param domainAxis the domain axis. 207 * @param rangeAxis the range axis. 208 * @param dataset the dataset. 209 * @param series the series index (zero-based). 210 * @param item the item index (zero-based). 211 * @param crosshairState information about crosshairs on a plot. 212 * @param pass the pass index. 213 */ 214 @Override 215 public void drawItem(Graphics2D g2, XYItemRendererState state, 216 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 217 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 218 int series, int item, CrosshairState crosshairState, int pass) { 219 220 // setup for collecting optional entity info... 221 Shape entityArea; 222 EntityCollection entities = null; 223 if (info != null) { 224 entities = info.getOwner().getEntityCollection(); 225 } 226 227 TableXYDataset tdataset = (TableXYDataset) dataset; 228 PlotOrientation orientation = plot.getOrientation(); 229 230 // get the data point... 231 double x1 = dataset.getXValue(series, item); 232 double y1 = dataset.getYValue(series, item); 233 if (Double.isNaN(y1)) { 234 y1 = 0.0; 235 } 236 double[] stack1 = getStackValues(tdataset, series, item); 237 238 // get the previous point and the next point so we can calculate a 239 // "hot spot" for the area (used by the chart entity)... 240 double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); 241 double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); 242 if (Double.isNaN(y0)) { 243 y0 = 0.0; 244 } 245 double[] stack0 = getStackValues(tdataset, series, Math.max(item - 1, 246 0)); 247 248 int itemCount = dataset.getItemCount(series); 249 double x2 = dataset.getXValue(series, Math.min(item + 1, 250 itemCount - 1)); 251 double y2 = dataset.getYValue(series, Math.min(item + 1, 252 itemCount - 1)); 253 if (Double.isNaN(y2)) { 254 y2 = 0.0; 255 } 256 double[] stack2 = getStackValues(tdataset, series, Math.min(item + 1, 257 itemCount - 1)); 258 259 double xleft = (x0 + x1) / 2.0; 260 double xright = (x1 + x2) / 2.0; 261 double[] stackLeft = averageStackValues(stack0, stack1); 262 double[] stackRight = averageStackValues(stack1, stack2); 263 double[] adjStackLeft = adjustedStackValues(stack0, stack1); 264 double[] adjStackRight = adjustedStackValues(stack1, stack2); 265 266 RectangleEdge edge0 = plot.getDomainAxisEdge(); 267 268 float transX1 = (float) domainAxis.valueToJava2D(x1, dataArea, edge0); 269 float transXLeft = (float) domainAxis.valueToJava2D(xleft, dataArea, 270 edge0); 271 float transXRight = (float) domainAxis.valueToJava2D(xright, dataArea, 272 edge0); 273 274 if (this.roundXCoordinates) { 275 transX1 = Math.round(transX1); 276 transXLeft = Math.round(transXLeft); 277 transXRight = Math.round(transXRight); 278 } 279 float transY1; 280 281 RectangleEdge edge1 = plot.getRangeAxisEdge(); 282 283 GeneralPath left = new GeneralPath(); 284 GeneralPath right = new GeneralPath(); 285 if (y1 >= 0.0) { // handle positive value 286 transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[1], dataArea, 287 edge1); 288 float transStack1 = (float) rangeAxis.valueToJava2D(stack1[1], 289 dataArea, edge1); 290 float transStackLeft = (float) rangeAxis.valueToJava2D( 291 adjStackLeft[1], dataArea, edge1); 292 293 // LEFT POLYGON 294 if (y0 >= 0.0) { 295 double yleft = (y0 + y1) / 2.0 + stackLeft[1]; 296 float transYLeft 297 = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1); 298 if (orientation == PlotOrientation.VERTICAL) { 299 left.moveTo(transX1, transY1); 300 left.lineTo(transX1, transStack1); 301 left.lineTo(transXLeft, transStackLeft); 302 left.lineTo(transXLeft, transYLeft); 303 } 304 else { 305 left.moveTo(transY1, transX1); 306 left.lineTo(transStack1, transX1); 307 left.lineTo(transStackLeft, transXLeft); 308 left.lineTo(transYLeft, transXLeft); 309 } 310 left.closePath(); 311 } 312 else { 313 if (orientation == PlotOrientation.VERTICAL) { 314 left.moveTo(transX1, transStack1); 315 left.lineTo(transX1, transY1); 316 left.lineTo(transXLeft, transStackLeft); 317 } 318 else { 319 left.moveTo(transStack1, transX1); 320 left.lineTo(transY1, transX1); 321 left.lineTo(transStackLeft, transXLeft); 322 } 323 left.closePath(); 324 } 325 326 float transStackRight = (float) rangeAxis.valueToJava2D( 327 adjStackRight[1], dataArea, edge1); 328 // RIGHT POLYGON 329 if (y2 >= 0.0) { 330 double yright = (y1 + y2) / 2.0 + stackRight[1]; 331 float transYRight 332 = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1); 333 if (orientation == PlotOrientation.VERTICAL) { 334 right.moveTo(transX1, transStack1); 335 right.lineTo(transX1, transY1); 336 right.lineTo(transXRight, transYRight); 337 right.lineTo(transXRight, transStackRight); 338 } 339 else { 340 right.moveTo(transStack1, transX1); 341 right.lineTo(transY1, transX1); 342 right.lineTo(transYRight, transXRight); 343 right.lineTo(transStackRight, transXRight); 344 } 345 right.closePath(); 346 } 347 else { 348 if (orientation == PlotOrientation.VERTICAL) { 349 right.moveTo(transX1, transStack1); 350 right.lineTo(transX1, transY1); 351 right.lineTo(transXRight, transStackRight); 352 } 353 else { 354 right.moveTo(transStack1, transX1); 355 right.lineTo(transY1, transX1); 356 right.lineTo(transStackRight, transXRight); 357 } 358 right.closePath(); 359 } 360 } 361 else { // handle negative value 362 transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[0], dataArea, 363 edge1); 364 float transStack1 = (float) rangeAxis.valueToJava2D(stack1[0], 365 dataArea, edge1); 366 float transStackLeft = (float) rangeAxis.valueToJava2D( 367 adjStackLeft[0], dataArea, edge1); 368 369 // LEFT POLYGON 370 if (y0 >= 0.0) { 371 if (orientation == PlotOrientation.VERTICAL) { 372 left.moveTo(transX1, transStack1); 373 left.lineTo(transX1, transY1); 374 left.lineTo(transXLeft, transStackLeft); 375 } 376 else { 377 left.moveTo(transStack1, transX1); 378 left.lineTo(transY1, transX1); 379 left.lineTo(transStackLeft, transXLeft); 380 } 381 left.clone(); 382 } 383 else { 384 double yleft = (y0 + y1) / 2.0 + stackLeft[0]; 385 float transYLeft = (float) rangeAxis.valueToJava2D(yleft, 386 dataArea, edge1); 387 if (orientation == PlotOrientation.VERTICAL) { 388 left.moveTo(transX1, transY1); 389 left.lineTo(transX1, transStack1); 390 left.lineTo(transXLeft, transStackLeft); 391 left.lineTo(transXLeft, transYLeft); 392 } 393 else { 394 left.moveTo(transY1, transX1); 395 left.lineTo(transStack1, transX1); 396 left.lineTo(transStackLeft, transXLeft); 397 left.lineTo(transYLeft, transXLeft); 398 } 399 left.closePath(); 400 } 401 float transStackRight = (float) rangeAxis.valueToJava2D( 402 adjStackRight[0], dataArea, edge1); 403 404 // RIGHT POLYGON 405 if (y2 >= 0.0) { 406 if (orientation == PlotOrientation.VERTICAL) { 407 right.moveTo(transX1, transStack1); 408 right.lineTo(transX1, transY1); 409 right.lineTo(transXRight, transStackRight); 410 } 411 else { 412 right.moveTo(transStack1, transX1); 413 right.lineTo(transY1, transX1); 414 right.lineTo(transStackRight, transXRight); 415 } 416 right.closePath(); 417 } 418 else { 419 double yright = (y1 + y2) / 2.0 + stackRight[0]; 420 float transYRight = (float) rangeAxis.valueToJava2D(yright, 421 dataArea, edge1); 422 if (orientation == PlotOrientation.VERTICAL) { 423 right.moveTo(transX1, transStack1); 424 right.lineTo(transX1, transY1); 425 right.lineTo(transXRight, transYRight); 426 right.lineTo(transXRight, transStackRight); 427 } 428 else { 429 right.moveTo(transStack1, transX1); 430 right.lineTo(transY1, transX1); 431 right.lineTo(transYRight, transXRight); 432 right.lineTo(transStackRight, transXRight); 433 } 434 right.closePath(); 435 } 436 } 437 438 // Get series Paint and Stroke 439 Paint itemPaint = getItemPaint(series, item); 440 if (pass == 0) { 441 g2.setPaint(itemPaint); 442 g2.fill(left); 443 g2.fill(right); 444 } 445 446 // add an entity for the item... 447 if (entities != null) { 448 GeneralPath gp = new GeneralPath(left); 449 gp.append(right, false); 450 entityArea = gp; 451 addEntity(entities, entityArea, dataset, series, item, 452 transX1, transY1); 453 } 454 455 } 456 457 /** 458 * Calculates the stacked values (one positive and one negative) of all 459 * series up to, but not including, <code>series</code> for the specified 460 * item. It returns [0.0, 0.0] if <code>series</code> is the first series. 461 * 462 * @param dataset the dataset (<code>null</code> not permitted). 463 * @param series the series index. 464 * @param index the item index. 465 * 466 * @return An array containing the cumulative negative and positive values 467 * for all series values up to but excluding <code>series</code> 468 * for <code>index</code>. 469 */ 470 private double[] getStackValues(TableXYDataset dataset, 471 int series, int index) { 472 double[] result = new double[2]; 473 for (int i = 0; i < series; i++) { 474 double v = dataset.getYValue(i, index); 475 if (!Double.isNaN(v)) { 476 if (v >= 0.0) { 477 result[1] += v; 478 } 479 else { 480 result[0] += v; 481 } 482 } 483 } 484 return result; 485 } 486 487 /** 488 * Returns a pair of "stack" values calculated as the mean of the two 489 * specified stack value pairs. 490 * 491 * @param stack1 the first stack pair. 492 * @param stack2 the second stack pair. 493 * 494 * @return A pair of average stack values. 495 */ 496 private double[] averageStackValues(double[] stack1, double[] stack2) { 497 double[] result = new double[2]; 498 result[0] = (stack1[0] + stack2[0]) / 2.0; 499 result[1] = (stack1[1] + stack2[1]) / 2.0; 500 return result; 501 } 502 503 /** 504 * Calculates adjusted stack values from the supplied values. The value is 505 * the mean of the supplied values, unless either of the supplied values 506 * is zero, in which case the adjusted value is zero also. 507 * 508 * @param stack1 the first stack pair. 509 * @param stack2 the second stack pair. 510 * 511 * @return A pair of average stack values. 512 */ 513 private double[] adjustedStackValues(double[] stack1, double[] stack2) { 514 double[] result = new double[2]; 515 if (stack1[0] == 0.0 || stack2[0] == 0.0) { 516 result[0] = 0.0; 517 } 518 else { 519 result[0] = (stack1[0] + stack2[0]) / 2.0; 520 } 521 if (stack1[1] == 0.0 || stack2[1] == 0.0) { 522 result[1] = 0.0; 523 } 524 else { 525 result[1] = (stack1[1] + stack2[1]) / 2.0; 526 } 527 return result; 528 } 529 530 /** 531 * Tests this renderer for equality with an arbitrary object. 532 * 533 * @param obj the object (<code>null</code> permitted). 534 * 535 * @return A boolean. 536 */ 537 @Override 538 public boolean equals(Object obj) { 539 if (obj == this) { 540 return true; 541 } 542 if (!(obj instanceof StackedXYAreaRenderer2)) { 543 return false; 544 } 545 StackedXYAreaRenderer2 that = (StackedXYAreaRenderer2) obj; 546 if (this.roundXCoordinates != that.roundXCoordinates) { 547 return false; 548 } 549 return super.equals(obj); 550 } 551 552 /** 553 * Returns a clone of the renderer. 554 * 555 * @return A clone. 556 * 557 * @throws CloneNotSupportedException if the renderer cannot be cloned. 558 */ 559 @Override 560 public Object clone() throws CloneNotSupportedException { 561 return super.clone(); 562 } 563 564}