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