001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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-2009, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: Mofeed Shahin;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 * Peter Kolb (patch 2497611);
035 *
036 * Changes
037 * -------
038 * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG);
039 * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with
040 * StatisticalBarRenderer (DG);
041 * ------------- JFREECHART 1.0.x ---------------------------------------------
042 * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering
043 * plots with horizontal orientation (DG);
044 * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG);
045 * 01-Jun-2007 : Return early from drawItem() method if item is not
046 * visible (DG);
047 * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert
048 * to the drawing behaviour of LineAndShapeRenderer (DG);
049 * 27-Sep-2007 : Added offset option to match new option in
050 * LineAndShapeRenderer (DG);
051 * 14-Jan-2009 : Added support for seriesVisible flags (PK);
052 * 23-Jan-2009 : Observe useFillPaint and drawOutlines flags (PK);
053 * 23-Jan-2009 : In drawItem, divide code into passes (DG);
054 * 05-Feb-2009 : Added errorIndicatorStroke field (DG);
055 * 01-Apr-2009 : Added override for findRangeBounds(), and fixed NPE in
056 * creating item entities (DG);
057 */
058
059 package org.jfree.chart.renderer.category;
060
061 import java.awt.Graphics2D;
062 import java.awt.Paint;
063 import java.awt.Shape;
064 import java.awt.Stroke;
065 import java.awt.geom.Line2D;
066 import java.awt.geom.Rectangle2D;
067 import java.io.IOException;
068 import java.io.ObjectInputStream;
069 import java.io.ObjectOutputStream;
070 import java.io.Serializable;
071
072 import org.jfree.chart.HashUtilities;
073 import org.jfree.chart.axis.CategoryAxis;
074 import org.jfree.chart.axis.ValueAxis;
075 import org.jfree.chart.entity.EntityCollection;
076 import org.jfree.chart.event.RendererChangeEvent;
077 import org.jfree.chart.plot.CategoryPlot;
078 import org.jfree.chart.plot.PlotOrientation;
079 import org.jfree.data.Range;
080 import org.jfree.data.category.CategoryDataset;
081 import org.jfree.data.statistics.StatisticalCategoryDataset;
082 import org.jfree.io.SerialUtilities;
083 import org.jfree.ui.RectangleEdge;
084 import org.jfree.util.ObjectUtilities;
085 import org.jfree.util.PaintUtilities;
086 import org.jfree.util.PublicCloneable;
087 import org.jfree.util.ShapeUtilities;
088
089 /**
090 * A renderer that draws shapes for each data item, and lines between data
091 * items. Each point has a mean value and a standard deviation line. For use
092 * with the {@link CategoryPlot} class. The example shown
093 * here is generated by the <code>StatisticalLineChartDemo1.java</code> program
094 * included in the JFreeChart Demo Collection:
095 * <br><br>
096 * <img src="../../../../../images/StatisticalLineRendererSample.png"
097 * alt="StatisticalLineRendererSample.png" />
098 */
099 public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer
100 implements Cloneable, PublicCloneable, Serializable {
101
102 /** For serialization. */
103 private static final long serialVersionUID = -3557517173697777579L;
104
105 /** The paint used to show the error indicator. */
106 private transient Paint errorIndicatorPaint;
107
108 /**
109 * The stroke used to draw the error indicators. If null, the renderer
110 * will use the itemOutlineStroke.
111 *
112 * @since 1.0.13
113 */
114 private transient Stroke errorIndicatorStroke;
115
116 /**
117 * Constructs a default renderer (draws shapes and lines).
118 */
119 public StatisticalLineAndShapeRenderer() {
120 this(true, true);
121 }
122
123 /**
124 * Constructs a new renderer.
125 *
126 * @param linesVisible draw lines?
127 * @param shapesVisible draw shapes?
128 */
129 public StatisticalLineAndShapeRenderer(boolean linesVisible,
130 boolean shapesVisible) {
131 super(linesVisible, shapesVisible);
132 this.errorIndicatorPaint = null;
133 this.errorIndicatorStroke = null;
134 }
135
136 /**
137 * Returns the paint used for the error indicators.
138 *
139 * @return The paint used for the error indicators (possibly
140 * <code>null</code>).
141 *
142 * @see #setErrorIndicatorPaint(Paint)
143 */
144 public Paint getErrorIndicatorPaint() {
145 return this.errorIndicatorPaint;
146 }
147
148 /**
149 * Sets the paint used for the error indicators (if <code>null</code>,
150 * the item paint is used instead) and sends a
151 * {@link RendererChangeEvent} to all registered listeners.
152 *
153 * @param paint the paint (<code>null</code> permitted).
154 *
155 * @see #getErrorIndicatorPaint()
156 */
157 public void setErrorIndicatorPaint(Paint paint) {
158 this.errorIndicatorPaint = paint;
159 fireChangeEvent();
160 }
161
162 /**
163 * Returns the stroke used for the error indicators.
164 *
165 * @return The stroke used for the error indicators (possibly
166 * <code>null</code>).
167 *
168 * @see #setErrorIndicatorStroke(Stroke)
169 *
170 * @since 1.0.13
171 */
172 public Stroke getErrorIndicatorStroke() {
173 return this.errorIndicatorStroke;
174 }
175
176 /**
177 * Sets the stroke used for the error indicators (if <code>null</code>,
178 * the item outline stroke is used instead) and sends a
179 * {@link RendererChangeEvent} to all registered listeners.
180 *
181 * @param stroke the stroke (<code>null</code> permitted).
182 *
183 * @see #getErrorIndicatorStroke()
184 *
185 * @since 1.0.13
186 */
187 public void setErrorIndicatorStroke(Stroke stroke) {
188 this.errorIndicatorStroke = stroke;
189 fireChangeEvent();
190 }
191
192 /**
193 * Returns the range of values the renderer requires to display all the
194 * items from the specified dataset.
195 *
196 * @param dataset the dataset (<code>null</code> permitted).
197 *
198 * @return The range (or <code>null</code> if the dataset is
199 * <code>null</code> or empty).
200 */
201 public Range findRangeBounds(CategoryDataset dataset) {
202 return findRangeBounds(dataset, true);
203 }
204
205 /**
206 * Draw a single data item.
207 *
208 * @param g2 the graphics device.
209 * @param state the renderer state.
210 * @param dataArea the area in which the data is drawn.
211 * @param plot the plot.
212 * @param domainAxis the domain axis.
213 * @param rangeAxis the range axis.
214 * @param dataset the dataset (a {@link StatisticalCategoryDataset} is
215 * required).
216 * @param row the row index (zero-based).
217 * @param column the column index (zero-based).
218 * @param pass the pass.
219 */
220 public void drawItem(Graphics2D g2,
221 CategoryItemRendererState state,
222 Rectangle2D dataArea,
223 CategoryPlot plot,
224 CategoryAxis domainAxis,
225 ValueAxis rangeAxis,
226 CategoryDataset dataset,
227 int row,
228 int column,
229 int pass) {
230
231 // do nothing if item is not visible
232 if (!getItemVisible(row, column)) {
233 return;
234 }
235
236 // if the dataset is not a StatisticalCategoryDataset then just revert
237 // to the superclass (LineAndShapeRenderer) behaviour...
238 if (!(dataset instanceof StatisticalCategoryDataset)) {
239 super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
240 dataset, row, column, pass);
241 return;
242 }
243
244 int visibleRow = state.getVisibleSeriesIndex(row);
245 if (visibleRow < 0) {
246 return;
247 }
248 int visibleRowCount = state.getVisibleSeriesCount();
249
250 StatisticalCategoryDataset statDataset
251 = (StatisticalCategoryDataset) dataset;
252 Number meanValue = statDataset.getMeanValue(row, column);
253 if (meanValue == null) {
254 return;
255 }
256 PlotOrientation orientation = plot.getOrientation();
257
258 // current data point...
259 double x1;
260 if (getUseSeriesOffset()) {
261 x1 = domainAxis.getCategorySeriesMiddle(column,
262 dataset.getColumnCount(),
263 visibleRow, visibleRowCount,
264 getItemMargin(), dataArea, plot.getDomainAxisEdge());
265 }
266 else {
267 x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
268 dataArea, plot.getDomainAxisEdge());
269 }
270 double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea,
271 plot.getRangeAxisEdge());
272
273 // draw the standard deviation lines *before* the shapes (if they're
274 // visible) - it looks better if the shape fill colour is different to
275 // the line colour
276 Number sdv = statDataset.getStdDevValue(row, column);
277 if (pass == 1 && sdv != null) {
278 //standard deviation lines
279 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
280 double valueDelta = sdv.doubleValue();
281 double highVal, lowVal;
282 if ((meanValue.doubleValue() + valueDelta)
283 > rangeAxis.getRange().getUpperBound()) {
284 highVal = rangeAxis.valueToJava2D(
285 rangeAxis.getRange().getUpperBound(), dataArea,
286 yAxisLocation);
287 }
288 else {
289 highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
290 + valueDelta, dataArea, yAxisLocation);
291 }
292
293 if ((meanValue.doubleValue() + valueDelta)
294 < rangeAxis.getRange().getLowerBound()) {
295 lowVal = rangeAxis.valueToJava2D(
296 rangeAxis.getRange().getLowerBound(), dataArea,
297 yAxisLocation);
298 }
299 else {
300 lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
301 - valueDelta, dataArea, yAxisLocation);
302 }
303
304 if (this.errorIndicatorPaint != null) {
305 g2.setPaint(this.errorIndicatorPaint);
306 }
307 else {
308 g2.setPaint(getItemPaint(row, column));
309 }
310 if (this.errorIndicatorStroke != null) {
311 g2.setStroke(this.errorIndicatorStroke);
312 }
313 else {
314 g2.setStroke(getItemOutlineStroke(row, column));
315 }
316 Line2D line = new Line2D.Double();
317 if (orientation == PlotOrientation.HORIZONTAL) {
318 line.setLine(lowVal, x1, highVal, x1);
319 g2.draw(line);
320 line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d);
321 g2.draw(line);
322 line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d);
323 g2.draw(line);
324 }
325 else { // PlotOrientation.VERTICAL
326 line.setLine(x1, lowVal, x1, highVal);
327 g2.draw(line);
328 line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
329 g2.draw(line);
330 line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
331 g2.draw(line);
332 }
333
334 }
335
336 Shape hotspot = null;
337 if (pass == 1 && getItemShapeVisible(row, column)) {
338 Shape shape = getItemShape(row, column);
339 if (orientation == PlotOrientation.HORIZONTAL) {
340 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
341 }
342 else if (orientation == PlotOrientation.VERTICAL) {
343 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
344 }
345 hotspot = shape;
346
347 if (getItemShapeFilled(row, column)) {
348 if (getUseFillPaint()) {
349 g2.setPaint(getItemFillPaint(row, column));
350 }
351 else {
352 g2.setPaint(getItemPaint(row, column));
353 }
354 g2.fill(shape);
355 }
356 if (getDrawOutlines()) {
357 if (getUseOutlinePaint()) {
358 g2.setPaint(getItemOutlinePaint(row, column));
359 }
360 else {
361 g2.setPaint(getItemPaint(row, column));
362 }
363 g2.setStroke(getItemOutlineStroke(row, column));
364 g2.draw(shape);
365 }
366 // draw the item label if there is one...
367 if (isItemLabelVisible(row, column)) {
368 if (orientation == PlotOrientation.HORIZONTAL) {
369 drawItemLabel(g2, orientation, dataset, row, column,
370 y1, x1, (meanValue.doubleValue() < 0.0));
371 }
372 else if (orientation == PlotOrientation.VERTICAL) {
373 drawItemLabel(g2, orientation, dataset, row, column,
374 x1, y1, (meanValue.doubleValue() < 0.0));
375 }
376 }
377 }
378
379 if (pass == 0 && getItemLineVisible(row, column)) {
380 if (column != 0) {
381
382 Number previousValue = statDataset.getValue(row, column - 1);
383 if (previousValue != null) {
384
385 // previous data point...
386 double previous = previousValue.doubleValue();
387 double x0;
388 if (getUseSeriesOffset()) {
389 x0 = domainAxis.getCategorySeriesMiddle(
390 column - 1, dataset.getColumnCount(),
391 visibleRow, visibleRowCount,
392 getItemMargin(), dataArea,
393 plot.getDomainAxisEdge());
394 }
395 else {
396 x0 = domainAxis.getCategoryMiddle(column - 1,
397 getColumnCount(), dataArea,
398 plot.getDomainAxisEdge());
399 }
400 double y0 = rangeAxis.valueToJava2D(previous, dataArea,
401 plot.getRangeAxisEdge());
402
403 Line2D line = null;
404 if (orientation == PlotOrientation.HORIZONTAL) {
405 line = new Line2D.Double(y0, x0, y1, x1);
406 }
407 else if (orientation == PlotOrientation.VERTICAL) {
408 line = new Line2D.Double(x0, y0, x1, y1);
409 }
410 g2.setPaint(getItemPaint(row, column));
411 g2.setStroke(getItemStroke(row, column));
412 g2.draw(line);
413 }
414 }
415 }
416
417 if (pass == 1) {
418 // add an item entity, if this information is being collected
419 EntityCollection entities = state.getEntityCollection();
420 if (entities != null) {
421 addEntity(entities, hotspot, dataset, row, column, x1, y1);
422 }
423 }
424
425 }
426
427 /**
428 * Tests this renderer for equality with an arbitrary object.
429 *
430 * @param obj the object (<code>null</code> permitted).
431 *
432 * @return A boolean.
433 */
434 public boolean equals(Object obj) {
435 if (obj == this) {
436 return true;
437 }
438 if (!(obj instanceof StatisticalLineAndShapeRenderer)) {
439 return false;
440 }
441 StatisticalLineAndShapeRenderer that
442 = (StatisticalLineAndShapeRenderer) obj;
443 if (!PaintUtilities.equal(this.errorIndicatorPaint,
444 that.errorIndicatorPaint)) {
445 return false;
446 }
447 if (!ObjectUtilities.equal(this.errorIndicatorStroke,
448 that.errorIndicatorStroke)) {
449 return false;
450 }
451 return super.equals(obj);
452 }
453
454 /**
455 * Returns a hash code for this instance.
456 *
457 * @return A hash code.
458 */
459 public int hashCode() {
460 int hash = super.hashCode();
461 hash = HashUtilities.hashCode(hash, this.errorIndicatorPaint);
462 return hash;
463 }
464
465 /**
466 * Provides serialization support.
467 *
468 * @param stream the output stream.
469 *
470 * @throws IOException if there is an I/O error.
471 */
472 private void writeObject(ObjectOutputStream stream) throws IOException {
473 stream.defaultWriteObject();
474 SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
475 SerialUtilities.writeStroke(this.errorIndicatorStroke, stream);
476 }
477
478 /**
479 * Provides serialization support.
480 *
481 * @param stream the input stream.
482 *
483 * @throws IOException if there is an I/O error.
484 * @throws ClassNotFoundException if there is a classpath problem.
485 */
486 private void readObject(ObjectInputStream stream)
487 throws IOException, ClassNotFoundException {
488 stream.defaultReadObject();
489 this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
490 this.errorIndicatorStroke = SerialUtilities.readStroke(stream);
491 }
492
493 }