001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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 * StatisticalLineAndShapeRenderer.java 029 * ------------------------------------ 030 * (C) Copyright 2005, 2006, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: Mofeed Shahin; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * $Id: StatisticalLineAndShapeRenderer.java,v 1.4.2.7 2006/09/25 10:09:58 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG); 040 * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with 041 * StatisticalBarRenderer (DG); 042 * ------------- JFREECHART 1.0.0 --------------------------------------------- 043 * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering 044 * plots with horizontal orientation (DG); 045 * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG); 046 * 047 */ 048 049 package org.jfree.chart.renderer.category; 050 051 import java.awt.Graphics2D; 052 import java.awt.Paint; 053 import java.awt.Shape; 054 import java.awt.geom.Line2D; 055 import java.awt.geom.Rectangle2D; 056 import java.io.IOException; 057 import java.io.ObjectInputStream; 058 import java.io.ObjectOutputStream; 059 import java.io.Serializable; 060 061 import org.jfree.chart.axis.CategoryAxis; 062 import org.jfree.chart.axis.ValueAxis; 063 import org.jfree.chart.entity.CategoryItemEntity; 064 import org.jfree.chart.entity.EntityCollection; 065 import org.jfree.chart.event.RendererChangeEvent; 066 import org.jfree.chart.labels.CategoryToolTipGenerator; 067 import org.jfree.chart.plot.CategoryPlot; 068 import org.jfree.chart.plot.PlotOrientation; 069 import org.jfree.data.category.CategoryDataset; 070 import org.jfree.data.statistics.StatisticalCategoryDataset; 071 import org.jfree.io.SerialUtilities; 072 import org.jfree.ui.RectangleEdge; 073 import org.jfree.util.PaintUtilities; 074 import org.jfree.util.PublicCloneable; 075 import org.jfree.util.ShapeUtilities; 076 077 /** 078 * A renderer that draws shapes for each data item, and lines between data 079 * items. Each point has a mean value and a standard deviation line. For use 080 * with the {@link CategoryPlot} class. 081 */ 082 public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer 083 implements Cloneable, PublicCloneable, Serializable { 084 085 /** For serialization. */ 086 private static final long serialVersionUID = -3557517173697777579L; 087 088 /** The paint used to show the error indicator. */ 089 private transient Paint errorIndicatorPaint; 090 091 /** 092 * Constructs a default renderer (draws shapes and lines). 093 */ 094 public StatisticalLineAndShapeRenderer() { 095 this(true, true); 096 } 097 098 /** 099 * Constructs a new renderer. 100 * 101 * @param linesVisible draw lines? 102 * @param shapesVisible draw shapes? 103 */ 104 public StatisticalLineAndShapeRenderer(boolean linesVisible, 105 boolean shapesVisible) { 106 super(linesVisible, shapesVisible); 107 this.errorIndicatorPaint = null; 108 } 109 110 /** 111 * Returns the paint used for the error indicators. 112 * 113 * @return The paint used for the error indicators (possibly 114 * <code>null</code>). 115 */ 116 public Paint getErrorIndicatorPaint() { 117 return this.errorIndicatorPaint; 118 } 119 120 /** 121 * Sets the paint used for the error indicators (if <code>null</code>, 122 * the item outline paint is used instead) 123 * 124 * @param paint the paint (<code>null</code> permitted). 125 */ 126 public void setErrorIndicatorPaint(Paint paint) { 127 this.errorIndicatorPaint = paint; 128 notifyListeners(new RendererChangeEvent(this)); 129 } 130 131 /** 132 * Draw a single data item. 133 * 134 * @param g2 the graphics device. 135 * @param state the renderer state. 136 * @param dataArea the area in which the data is drawn. 137 * @param plot the plot. 138 * @param domainAxis the domain axis. 139 * @param rangeAxis the range axis. 140 * @param dataset the dataset (a {@link StatisticalCategoryDataset} is 141 * required). 142 * @param row the row index (zero-based). 143 * @param column the column index (zero-based). 144 * @param pass the pass. 145 */ 146 public void drawItem(Graphics2D g2, 147 CategoryItemRendererState state, 148 Rectangle2D dataArea, 149 CategoryPlot plot, 150 CategoryAxis domainAxis, 151 ValueAxis rangeAxis, 152 CategoryDataset dataset, 153 int row, 154 int column, 155 int pass) { 156 157 // nothing is drawn for null... 158 Number v = dataset.getValue(row, column); 159 if (v == null) { 160 return; 161 } 162 163 StatisticalCategoryDataset statData 164 = (StatisticalCategoryDataset) dataset; 165 166 Number meanValue = statData.getMeanValue(row, column); 167 168 PlotOrientation orientation = plot.getOrientation(); 169 170 // current data point... 171 double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 172 dataArea, plot.getDomainAxisEdge()); 173 174 double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea, 175 plot.getRangeAxisEdge()); 176 177 Shape shape = getItemShape(row, column); 178 if (orientation == PlotOrientation.HORIZONTAL) { 179 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1); 180 } 181 else if (orientation == PlotOrientation.VERTICAL) { 182 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1); 183 } 184 if (getItemShapeVisible(row, column)) { 185 186 if (getItemShapeFilled(row, column)) { 187 g2.setPaint(getItemPaint(row, column)); 188 g2.fill(shape); 189 } 190 else { 191 if (getUseOutlinePaint()) { 192 g2.setPaint(getItemOutlinePaint(row, column)); 193 } 194 else { 195 g2.setPaint(getItemPaint(row, column)); 196 } 197 g2.setStroke(getItemOutlineStroke(row, column)); 198 g2.draw(shape); 199 } 200 } 201 202 if (getItemLineVisible(row, column)) { 203 if (column != 0) { 204 205 Number previousValue = statData.getValue(row, column - 1); 206 if (previousValue != null) { 207 208 // previous data point... 209 double previous = previousValue.doubleValue(); 210 double x0 = domainAxis.getCategoryMiddle(column - 1, 211 getColumnCount(), dataArea, 212 plot.getDomainAxisEdge()); 213 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 214 plot.getRangeAxisEdge()); 215 216 Line2D line = null; 217 if (orientation == PlotOrientation.HORIZONTAL) { 218 line = new Line2D.Double(y0, x0, y1, x1); 219 } 220 else if (orientation == PlotOrientation.VERTICAL) { 221 line = new Line2D.Double(x0, y0, x1, y1); 222 } 223 g2.setPaint(getItemPaint(row, column)); 224 g2.setStroke(getItemStroke(row, column)); 225 g2.draw(line); 226 } 227 } 228 } 229 230 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 231 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 232 double rectX = domainAxis.getCategoryStart(column, getColumnCount(), 233 dataArea, xAxisLocation); 234 235 rectX = rectX + row * state.getBarWidth(); 236 237 g2.setPaint(getItemPaint(row, column)); 238 239 //standard deviation lines 240 double valueDelta = statData.getStdDevValue(row, column).doubleValue(); 241 242 double highVal, lowVal; 243 if ((meanValue.doubleValue() + valueDelta) 244 > rangeAxis.getRange().getUpperBound()) { 245 highVal = rangeAxis.valueToJava2D( 246 rangeAxis.getRange().getUpperBound(), dataArea, 247 yAxisLocation); 248 } 249 else { 250 highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 251 + valueDelta, dataArea, yAxisLocation); 252 } 253 254 if ((meanValue.doubleValue() + valueDelta) 255 < rangeAxis.getRange().getLowerBound()) { 256 lowVal = rangeAxis.valueToJava2D( 257 rangeAxis.getRange().getLowerBound(), dataArea, 258 yAxisLocation); 259 } 260 else { 261 lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 262 - valueDelta, dataArea, yAxisLocation); 263 } 264 265 if (this.errorIndicatorPaint != null) { 266 g2.setPaint(this.errorIndicatorPaint); 267 } 268 else { 269 g2.setPaint(getItemPaint(row, column)); 270 } 271 Line2D line = new Line2D.Double(); 272 if (orientation == PlotOrientation.HORIZONTAL) { 273 line.setLine(lowVal, x1, highVal, x1); 274 g2.draw(line); 275 line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d); 276 g2.draw(line); 277 line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d); 278 g2.draw(line); 279 } 280 else { // PlotOrientation.VERTICAL 281 line.setLine(x1, lowVal, x1, highVal); 282 g2.draw(line); 283 line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal); 284 g2.draw(line); 285 line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal); 286 g2.draw(line); 287 } 288 289 // draw the item label if there is one... 290 if (isItemLabelVisible(row, column)) { 291 if (orientation == PlotOrientation.HORIZONTAL) { 292 drawItemLabel(g2, orientation, dataset, row, column, 293 y1, x1, (meanValue.doubleValue() < 0.0)); 294 } 295 else if (orientation == PlotOrientation.VERTICAL) { 296 drawItemLabel(g2, orientation, dataset, row, column, 297 x1, y1, (meanValue.doubleValue() < 0.0)); 298 } 299 } 300 301 // collect entity and tool tip information... 302 if (state.getInfo() != null) { 303 EntityCollection entities = state.getEntityCollection(); 304 if (entities != null && shape != null) { 305 String tip = null; 306 CategoryToolTipGenerator tipster = getToolTipGenerator(row, 307 column); 308 if (tipster != null) { 309 tip = tipster.generateToolTip(dataset, row, column); 310 } 311 String url = null; 312 if (getItemURLGenerator(row, column) != null) { 313 url = getItemURLGenerator(row, column).generateURL( 314 dataset, row, column); 315 } 316 CategoryItemEntity entity = new CategoryItemEntity(shape, tip, 317 url, dataset, row, dataset.getColumnKey(column), 318 column); 319 entities.add(entity); 320 321 } 322 323 } 324 325 } 326 327 /** 328 * Tests this renderer for equality with an arbitrary object. 329 * 330 * @param obj the object (<code>null</code> permitted). 331 * 332 * @return A boolean. 333 */ 334 public boolean equals(Object obj) { 335 if (obj == this) { 336 return true; 337 } 338 if (!(obj instanceof StatisticalLineAndShapeRenderer)) { 339 return false; 340 } 341 if (!super.equals(obj)) { 342 return false; 343 } 344 StatisticalLineAndShapeRenderer that 345 = (StatisticalLineAndShapeRenderer) obj; 346 if (!PaintUtilities.equal(this.errorIndicatorPaint, 347 that.errorIndicatorPaint)) { 348 return false; 349 } 350 return true; 351 } 352 353 /** 354 * Provides serialization support. 355 * 356 * @param stream the output stream. 357 * 358 * @throws IOException if there is an I/O error. 359 */ 360 private void writeObject(ObjectOutputStream stream) throws IOException { 361 stream.defaultWriteObject(); 362 SerialUtilities.writePaint(this.errorIndicatorPaint, stream); 363 } 364 365 /** 366 * Provides serialization support. 367 * 368 * @param stream the input stream. 369 * 370 * @throws IOException if there is an I/O error. 371 * @throws ClassNotFoundException if there is a classpath problem. 372 */ 373 private void readObject(ObjectInputStream stream) 374 throws IOException, ClassNotFoundException { 375 stream.defaultReadObject(); 376 this.errorIndicatorPaint = SerialUtilities.readPaint(stream); 377 } 378 379 }