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