Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ----------------------- 28: * XYStepAreaRenderer.java 29: * ----------------------- 30: * (C) Copyright 2003-2006, by Matthias Rose and Contributors. 31: * 32: * Original Author: Matthias Rose (based on XYAreaRenderer.java); 33: * Contributor(s): David Gilbert (for Object Refinery Limited); 34: * 35: * $Id: XYStepAreaRenderer.java,v 1.7.2.4 2006/07/06 10:27:57 mungady Exp $ 36: * 37: * Changes: 38: * -------- 39: * 07-Oct-2003 : Version 1, contributed by Matthias Rose (DG); 40: * 10-Feb-2004 : Added some getter and setter methods (DG); 41: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 42: * XYToolTipGenerator --> XYItemLabelGenerator (DG); 43: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 44: * getYValue() (DG); 45: * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 46: * 06-Jul-2005 : Renamed get/setPlotShapes() --> get/setShapesVisible() (DG); 47: * ------------- JFREECHART 1.0.0 --------------------------------------------- 48: * 06-Jul-2006 : Modified to call dataset methods that return double 49: * primitives only (DG); 50: * 51: */ 52: 53: package org.jfree.chart.renderer.xy; 54: 55: import java.awt.Graphics2D; 56: import java.awt.Paint; 57: import java.awt.Polygon; 58: import java.awt.Shape; 59: import java.awt.Stroke; 60: import java.awt.geom.Rectangle2D; 61: import java.io.Serializable; 62: 63: import org.jfree.chart.axis.ValueAxis; 64: import org.jfree.chart.entity.EntityCollection; 65: import org.jfree.chart.entity.XYItemEntity; 66: import org.jfree.chart.event.RendererChangeEvent; 67: import org.jfree.chart.labels.XYToolTipGenerator; 68: import org.jfree.chart.plot.CrosshairState; 69: import org.jfree.chart.plot.PlotOrientation; 70: import org.jfree.chart.plot.PlotRenderingInfo; 71: import org.jfree.chart.plot.XYPlot; 72: import org.jfree.chart.urls.XYURLGenerator; 73: import org.jfree.data.xy.XYDataset; 74: import org.jfree.util.PublicCloneable; 75: import org.jfree.util.ShapeUtilities; 76: 77: /** 78: * A step chart renderer that fills the area between the step and the x-axis. 79: */ 80: public class XYStepAreaRenderer extends AbstractXYItemRenderer 81: implements XYItemRenderer, 82: Cloneable, 83: PublicCloneable, 84: Serializable { 85: 86: /** For serialization. */ 87: private static final long serialVersionUID = -7311560779702649635L; 88: 89: /** Useful constant for specifying the type of rendering (shapes only). */ 90: public static final int SHAPES = 1; 91: 92: /** Useful constant for specifying the type of rendering (area only). */ 93: public static final int AREA = 2; 94: 95: /** 96: * Useful constant for specifying the type of rendering (area and shapes). 97: */ 98: public static final int AREA_AND_SHAPES = 3; 99: 100: /** A flag indicating whether or not shapes are drawn at each XY point. */ 101: private boolean shapesVisible; 102: 103: /** A flag that controls whether or not shapes are filled for ALL series. */ 104: private boolean shapesFilled; 105: 106: /** A flag indicating whether or not Area are drawn at each XY point. */ 107: private boolean plotArea; 108: 109: /** A flag that controls whether or not the outline is shown. */ 110: private boolean showOutline; 111: 112: /** Area of the complete series */ 113: protected transient Polygon pArea = null; 114: 115: /** 116: * The value on the range axis which defines the 'lower' border of the 117: * area. 118: */ 119: private double rangeBase; 120: 121: /** 122: * Constructs a new renderer. 123: */ 124: public XYStepAreaRenderer() { 125: this(AREA); 126: } 127: 128: /** 129: * Constructs a new renderer. 130: * 131: * @param type the type of the renderer. 132: */ 133: public XYStepAreaRenderer(int type) { 134: this(type, null, null); 135: } 136: 137: /** 138: * Constructs a new renderer. 139: * <p> 140: * To specify the type of renderer, use one of the constants: 141: * AREA, SHAPES or AREA_AND_SHAPES. 142: * 143: * @param type the type of renderer. 144: * @param toolTipGenerator the tool tip generator to use 145: * (<code>null</code> permitted). 146: * @param urlGenerator the URL generator (<code>null</code> permitted). 147: */ 148: public XYStepAreaRenderer(int type, 149: XYToolTipGenerator toolTipGenerator, 150: XYURLGenerator urlGenerator) { 151: 152: super(); 153: setBaseToolTipGenerator(toolTipGenerator); 154: setURLGenerator(urlGenerator); 155: 156: if (type == AREA) { 157: this.plotArea = true; 158: } 159: else if (type == SHAPES) { 160: this.shapesVisible = true; 161: } 162: else if (type == AREA_AND_SHAPES) { 163: this.plotArea = true; 164: this.shapesVisible = true; 165: } 166: this.showOutline = false; 167: } 168: 169: /** 170: * Returns a flag that controls whether or not outlines of the areas are 171: * drawn. 172: * 173: * @return The flag. 174: */ 175: public boolean isOutline() { 176: return this.showOutline; 177: } 178: 179: /** 180: * Sets a flag that controls whether or not outlines of the areas are 181: * drawn, and sends a {@link RendererChangeEvent} to all registered 182: * listeners. 183: * 184: * @param show the flag. 185: */ 186: public void setOutline(boolean show) { 187: this.showOutline = show; 188: notifyListeners(new RendererChangeEvent(this)); 189: } 190: 191: /** 192: * Returns true if shapes are being plotted by the renderer. 193: * 194: * @return <code>true</code> if shapes are being plotted by the renderer. 195: */ 196: public boolean getShapesVisible() { 197: return this.shapesVisible; 198: } 199: 200: /** 201: * Sets the flag that controls whether or not shapes are displayed for each 202: * data item, and sends a {@link RendererChangeEvent} to all registered 203: * listeners. 204: * 205: * @param flag the flag. 206: */ 207: public void setShapesVisible(boolean flag) { 208: this.shapesVisible = flag; 209: notifyListeners(new RendererChangeEvent(this)); 210: } 211: 212: /** 213: * Returns the flag that controls whether or not the shapes are filled. 214: * 215: * @return A boolean. 216: */ 217: public boolean isShapesFilled() { 218: return this.shapesFilled; 219: } 220: 221: /** 222: * Sets the 'shapes filled' for ALL series. 223: * 224: * @param filled the flag. 225: */ 226: public void setShapesFilled(boolean filled) { 227: this.shapesFilled = filled; 228: notifyListeners(new RendererChangeEvent(this)); 229: } 230: 231: /** 232: * Returns true if Area is being plotted by the renderer. 233: * 234: * @return <code>true</code> if Area is being plotted by the renderer. 235: */ 236: public boolean getPlotArea() { 237: return this.plotArea; 238: } 239: 240: /** 241: * Sets a flag that controls whether or not areas are drawn for each data 242: * item. 243: * 244: * @param flag the flag. 245: */ 246: public void setPlotArea(boolean flag) { 247: this.plotArea = flag; 248: notifyListeners(new RendererChangeEvent(this)); 249: } 250: 251: /** 252: * Returns the value on the range axis which defines the 'lower' border of 253: * the area. 254: * 255: * @return <code>double</code> the value on the range axis which defines 256: * the 'lower' border of the area. 257: */ 258: public double getRangeBase() { 259: return this.rangeBase; 260: } 261: 262: /** 263: * Sets the value on the range axis which defines the default border of the 264: * area. E.g. setRangeBase(Double.NEGATIVE_INFINITY) lets areas always 265: * reach the lower border of the plotArea. 266: * 267: * @param val the value on the range axis which defines the default border 268: * of the area. 269: */ 270: public void setRangeBase(double val) { 271: this.rangeBase = val; 272: notifyListeners(new RendererChangeEvent(this)); 273: } 274: 275: /** 276: * Initialises the renderer. Here we calculate the Java2D y-coordinate for 277: * zero, since all the bars have their bases fixed at zero. 278: * 279: * @param g2 the graphics device. 280: * @param dataArea the area inside the axes. 281: * @param plot the plot. 282: * @param data the data. 283: * @param info an optional info collection object to return data back to 284: * the caller. 285: * 286: * @return The number of passes required by the renderer. 287: */ 288: public XYItemRendererState initialise(Graphics2D g2, 289: Rectangle2D dataArea, 290: XYPlot plot, 291: XYDataset data, 292: PlotRenderingInfo info) { 293: 294: return super.initialise(g2, dataArea, plot, data, info); 295: 296: } 297: 298: 299: /** 300: * Draws the visual representation of a single data item. 301: * 302: * @param g2 the graphics device. 303: * @param state the renderer state. 304: * @param dataArea the area within which the data is being drawn. 305: * @param info collects information about the drawing. 306: * @param plot the plot (can be used to obtain standard color information 307: * etc). 308: * @param domainAxis the domain axis. 309: * @param rangeAxis the range axis. 310: * @param dataset the dataset. 311: * @param series the series index (zero-based). 312: * @param item the item index (zero-based). 313: * @param crosshairState crosshair information for the plot 314: * (<code>null</code> permitted). 315: * @param pass the pass index. 316: */ 317: public void drawItem(Graphics2D g2, 318: XYItemRendererState state, 319: Rectangle2D dataArea, 320: PlotRenderingInfo info, 321: XYPlot plot, 322: ValueAxis domainAxis, 323: ValueAxis rangeAxis, 324: XYDataset dataset, 325: int series, 326: int item, 327: CrosshairState crosshairState, 328: int pass) { 329: 330: PlotOrientation orientation = plot.getOrientation(); 331: 332: // Get the item count for the series, so that we can know which is the 333: // end of the series. 334: int itemCount = dataset.getItemCount(series); 335: 336: Paint paint = getItemPaint(series, item); 337: Stroke seriesStroke = getItemStroke(series, item); 338: g2.setPaint(paint); 339: g2.setStroke(seriesStroke); 340: 341: // get the data point... 342: double x1 = dataset.getXValue(series, item); 343: double y1 = dataset.getYValue(series, item); 344: double x = x1; 345: double y = Double.isNaN(y1) ? getRangeBase() : y1; 346: double transX1 = domainAxis.valueToJava2D(x, dataArea, 347: plot.getDomainAxisEdge()); 348: double transY1 = rangeAxis.valueToJava2D(y, dataArea, 349: plot.getRangeAxisEdge()); 350: 351: // avoid possible sun.dc.pr.PRException: endPath: bad path 352: transY1 = restrictValueToDataArea(transY1, plot, dataArea); 353: 354: if (this.pArea == null && !Double.isNaN(y1)) { 355: 356: // Create a new Area for the series 357: this.pArea = new Polygon(); 358: 359: // start from Y = rangeBase 360: double transY2 = rangeAxis.valueToJava2D(getRangeBase(), dataArea, 361: plot.getRangeAxisEdge()); 362: 363: // avoid possible sun.dc.pr.PRException: endPath: bad path 364: transY2 = restrictValueToDataArea(transY2, plot, dataArea); 365: 366: // The first point is (x, this.baseYValue) 367: if (orientation == PlotOrientation.VERTICAL) { 368: this.pArea.addPoint((int) transX1, (int) transY2); 369: } 370: else if (orientation == PlotOrientation.HORIZONTAL) { 371: this.pArea.addPoint((int) transY2, (int) transX1); 372: } 373: } 374: 375: double transX0 = 0; 376: double transY0 = restrictValueToDataArea( 377: getRangeBase(), plot, dataArea 378: ); 379: 380: double x0; 381: double y0; 382: if (item > 0) { 383: // get the previous data point... 384: x0 = dataset.getXValue(series, item - 1); 385: y0 = Double.isNaN(y1) ? y1 : dataset.getYValue(series, item - 1); 386: 387: x = x0; 388: y = Double.isNaN(y0) ? getRangeBase() : y0; 389: transX0 = domainAxis.valueToJava2D(x, dataArea, 390: plot.getDomainAxisEdge()); 391: transY0 = rangeAxis.valueToJava2D(y, dataArea, 392: plot.getRangeAxisEdge()); 393: 394: // avoid possible sun.dc.pr.PRException: endPath: bad path 395: transY0 = restrictValueToDataArea(transY0, plot, dataArea); 396: 397: if (Double.isNaN(y1)) { 398: // NULL value -> insert point on base line 399: // instead of 'step point' 400: transX1 = transX0; 401: transY0 = transY1; 402: } 403: if (transY0 != transY1) { 404: // not just a horizontal bar but need to perform a 'step'. 405: if (orientation == PlotOrientation.VERTICAL) { 406: this.pArea.addPoint((int) transX1, (int) transY0); 407: } 408: else if (orientation == PlotOrientation.HORIZONTAL) { 409: this.pArea.addPoint((int) transY0, (int) transX1); 410: } 411: } 412: } 413: 414: Shape shape = null; 415: if (!Double.isNaN(y1)) { 416: // Add each point to Area (x, y) 417: if (orientation == PlotOrientation.VERTICAL) { 418: this.pArea.addPoint((int) transX1, (int) transY1); 419: } 420: else if (orientation == PlotOrientation.HORIZONTAL) { 421: this.pArea.addPoint((int) transY1, (int) transX1); 422: } 423: 424: if (getShapesVisible()) { 425: shape = getItemShape(series, item); 426: if (orientation == PlotOrientation.VERTICAL) { 427: shape = ShapeUtilities.createTranslatedShape(shape, 428: transX1, transY1); 429: } 430: else if (orientation == PlotOrientation.HORIZONTAL) { 431: shape = ShapeUtilities.createTranslatedShape(shape, 432: transY1, transX1); 433: } 434: if (isShapesFilled()) { 435: g2.fill(shape); 436: } 437: else { 438: g2.draw(shape); 439: } 440: } 441: else { 442: if (orientation == PlotOrientation.VERTICAL) { 443: shape = new Rectangle2D.Double(transX1 - 2, transY1 - 2, 444: 4.0, 4.0); 445: } 446: else if (orientation == PlotOrientation.HORIZONTAL) { 447: shape = new Rectangle2D.Double(transY1 - 2, transX1 - 2, 448: 4.0, 4.0); 449: } 450: } 451: } 452: 453: // Check if the item is the last item for the series or if it 454: // is a NULL value and number of items > 0. We can't draw an area for 455: // a single point. 456: if (getPlotArea() && item > 0 && this.pArea != null 457: && (item == (itemCount - 1) || Double.isNaN(y1))) { 458: 459: double transY2 = rangeAxis.valueToJava2D(getRangeBase(), dataArea, 460: plot.getRangeAxisEdge()); 461: 462: // avoid possible sun.dc.pr.PRException: endPath: bad path 463: transY2 = restrictValueToDataArea(transY2, plot, dataArea); 464: 465: if (orientation == PlotOrientation.VERTICAL) { 466: // Add the last point (x,0) 467: this.pArea.addPoint((int) transX1, (int) transY2); 468: } 469: else if (orientation == PlotOrientation.HORIZONTAL) { 470: // Add the last point (x,0) 471: this.pArea.addPoint((int) transY2, (int) transX1); 472: } 473: 474: // fill the polygon 475: g2.fill(this.pArea); 476: 477: // draw an outline around the Area. 478: if (isOutline()) { 479: g2.setStroke(plot.getOutlineStroke()); 480: g2.setPaint(plot.getOutlinePaint()); 481: g2.draw(this.pArea); 482: } 483: 484: // start new area when needed (see above) 485: this.pArea = null; 486: } 487: 488: // do we need to update the crosshair values? 489: if (!Double.isNaN(y1)) { 490: updateCrosshairValues(crosshairState, x1, y1, transX1, transY1, 491: orientation); 492: } 493: 494: // collect entity and tool tip information... 495: if (state.getInfo() != null) { 496: EntityCollection entities = state.getEntityCollection(); 497: if (entities != null && shape != null) { 498: String tip = null; 499: XYToolTipGenerator generator 500: = getToolTipGenerator(series, item); 501: if (generator != null) { 502: tip = generator.generateToolTip(dataset, series, item); 503: } 504: String url = null; 505: if (getURLGenerator() != null) { 506: url = getURLGenerator().generateURL(dataset, series, item); 507: } 508: XYItemEntity entity = new XYItemEntity(shape, dataset, series, 509: item, tip, url); 510: entities.add(entity); 511: } 512: } 513: } 514: 515: /** 516: * Returns a clone of the renderer. 517: * 518: * @return A clone. 519: * 520: * @throws CloneNotSupportedException if the renderer cannot be cloned. 521: */ 522: public Object clone() throws CloneNotSupportedException { 523: return super.clone(); 524: } 525: 526: /** 527: * Helper method which returns a value if it lies 528: * inside the visible dataArea and otherwise the corresponding 529: * coordinate on the border of the dataArea. The PlotOrientation 530: * is taken into account. 531: * Useful to avoid possible sun.dc.pr.PRException: endPath: bad path 532: * which occurs when trying to draw lines/shapes which in large part 533: * lie outside of the visible dataArea. 534: * 535: * @param value the value which shall be 536: * @param dataArea the area within which the data is being drawn. 537: * @param plot the plot (can be used to obtain standard color 538: * information etc). 539: * @return <code>double</code> value inside the data area. 540: */ 541: protected static double restrictValueToDataArea(double value, 542: XYPlot plot, 543: Rectangle2D dataArea) { 544: double min = 0; 545: double max = 0; 546: if (plot.getOrientation() == PlotOrientation.VERTICAL) { 547: min = dataArea.getMinY(); 548: max = dataArea.getMaxY(); 549: } 550: else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 551: min = dataArea.getMinX(); 552: max = dataArea.getMaxX(); 553: } 554: if (value < min) { 555: value = min; 556: } 557: else if (value > max) { 558: value = max; 559: } 560: return value; 561: } 562: 563: }