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 }