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 * StatisticalBarRenderer.java
029 * ---------------------------
030 * (C) Copyright 2002-2007, by Pascal Collet and Contributors.
031 *
032 * Original Author: Pascal Collet;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 * Christian W. Zuckschwerdt;
035 *
036 * $Id: StatisticalBarRenderer.java,v 1.4.2.6 2007/02/02 15:52:07 mungady Exp $
037 *
038 * Changes
039 * -------
040 * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG);
041 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
042 * 24-Oct-2002 : Changes to dataset interface (DG);
043 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
044 * 05-Feb-2003 : Updates for new DefaultStatisticalCategoryDataset (DG);
045 * 25-Mar-2003 : Implemented Serializable (DG);
046 * 30-Jul-2003 : Modified entity constructor (CZ);
047 * 06-Oct-2003 : Corrected typo in exception message (DG);
048 * 05-Nov-2004 : Modified drawItem() signature (DG);
049 * 15-Jun-2005 : Added errorIndicatorPaint attribute (DG);
050 * ------------- JFREECHART 1.0.x ---------------------------------------------
051 * 19-May-2006 : Added support for tooltips and URLs (DG);
052 * 12-Jul-2006 : Added support for item labels (DG);
053 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
054 *
055 */
056
057 package org.jfree.chart.renderer.category;
058
059 import java.awt.Color;
060 import java.awt.Graphics2D;
061 import java.awt.Paint;
062 import java.awt.geom.Line2D;
063 import java.awt.geom.Rectangle2D;
064 import java.io.IOException;
065 import java.io.ObjectInputStream;
066 import java.io.ObjectOutputStream;
067 import java.io.Serializable;
068
069 import org.jfree.chart.axis.CategoryAxis;
070 import org.jfree.chart.axis.ValueAxis;
071 import org.jfree.chart.entity.EntityCollection;
072 import org.jfree.chart.event.RendererChangeEvent;
073 import org.jfree.chart.labels.CategoryItemLabelGenerator;
074 import org.jfree.chart.plot.CategoryPlot;
075 import org.jfree.chart.plot.PlotOrientation;
076 import org.jfree.data.category.CategoryDataset;
077 import org.jfree.data.statistics.StatisticalCategoryDataset;
078 import org.jfree.io.SerialUtilities;
079 import org.jfree.ui.RectangleEdge;
080 import org.jfree.util.PaintUtilities;
081 import org.jfree.util.PublicCloneable;
082
083 /**
084 * A renderer that handles the drawing a bar plot where
085 * each bar has a mean value and a standard deviation line.
086 */
087 public class StatisticalBarRenderer extends BarRenderer
088 implements CategoryItemRenderer,
089 Cloneable, PublicCloneable,
090 Serializable {
091
092 /** For serialization. */
093 private static final long serialVersionUID = -4986038395414039117L;
094
095 /** The paint used to show the error indicator. */
096 private transient Paint errorIndicatorPaint;
097
098 /**
099 * Default constructor.
100 */
101 public StatisticalBarRenderer() {
102 super();
103 this.errorIndicatorPaint = Color.gray;
104 }
105
106 /**
107 * Returns the paint used for the error indicators.
108 *
109 * @return The paint used for the error indicators (possibly
110 * <code>null</code>).
111 */
112 public Paint getErrorIndicatorPaint() {
113 return this.errorIndicatorPaint;
114 }
115
116 /**
117 * Sets the paint used for the error indicators (if <code>null</code>,
118 * the item outline paint is used instead)
119 *
120 * @param paint the paint (<code>null</code> permitted).
121 */
122 public void setErrorIndicatorPaint(Paint paint) {
123 this.errorIndicatorPaint = paint;
124 notifyListeners(new RendererChangeEvent(this));
125 }
126
127 /**
128 * Draws the bar with its standard deviation line range for a single
129 * (series, category) data item.
130 *
131 * @param g2 the graphics device.
132 * @param state the renderer state.
133 * @param dataArea the data area.
134 * @param plot the plot.
135 * @param domainAxis the domain axis.
136 * @param rangeAxis the range axis.
137 * @param data the data.
138 * @param row the row index (zero-based).
139 * @param column the column index (zero-based).
140 * @param pass the pass index.
141 */
142 public void drawItem(Graphics2D g2,
143 CategoryItemRendererState state,
144 Rectangle2D dataArea,
145 CategoryPlot plot,
146 CategoryAxis domainAxis,
147 ValueAxis rangeAxis,
148 CategoryDataset data,
149 int row,
150 int column,
151 int pass) {
152
153 // defensive check
154 if (!(data instanceof StatisticalCategoryDataset)) {
155 throw new IllegalArgumentException(
156 "Requires StatisticalCategoryDataset.");
157 }
158 StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data;
159
160 PlotOrientation orientation = plot.getOrientation();
161 if (orientation == PlotOrientation.HORIZONTAL) {
162 drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
163 rangeAxis, statData, row, column);
164 }
165 else if (orientation == PlotOrientation.VERTICAL) {
166 drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
167 statData, row, column);
168 }
169 }
170
171 /**
172 * Draws an item for a plot with a horizontal orientation.
173 *
174 * @param g2 the graphics device.
175 * @param state the renderer state.
176 * @param dataArea the data area.
177 * @param plot the plot.
178 * @param domainAxis the domain axis.
179 * @param rangeAxis the range axis.
180 * @param dataset the data.
181 * @param row the row index (zero-based).
182 * @param column the column index (zero-based).
183 */
184 protected void drawHorizontalItem(Graphics2D g2,
185 CategoryItemRendererState state,
186 Rectangle2D dataArea,
187 CategoryPlot plot,
188 CategoryAxis domainAxis,
189 ValueAxis rangeAxis,
190 StatisticalCategoryDataset dataset,
191 int row,
192 int column) {
193
194 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
195
196 // BAR Y
197 double rectY = domainAxis.getCategoryStart(column, getColumnCount(),
198 dataArea, xAxisLocation);
199
200 int seriesCount = getRowCount();
201 int categoryCount = getColumnCount();
202 if (seriesCount > 1) {
203 double seriesGap = dataArea.getHeight() * getItemMargin()
204 / (categoryCount * (seriesCount - 1));
205 rectY = rectY + row * (state.getBarWidth() + seriesGap);
206 }
207 else {
208 rectY = rectY + row * state.getBarWidth();
209 }
210
211 // BAR X
212 Number meanValue = dataset.getMeanValue(row, column);
213
214 double value = meanValue.doubleValue();
215 double base = 0.0;
216 double lclip = getLowerClip();
217 double uclip = getUpperClip();
218
219 if (uclip <= 0.0) { // cases 1, 2, 3 and 4
220 if (value >= uclip) {
221 return; // bar is not visible
222 }
223 base = uclip;
224 if (value <= lclip) {
225 value = lclip;
226 }
227 }
228 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
229 if (value >= uclip) {
230 value = uclip;
231 }
232 else {
233 if (value <= lclip) {
234 value = lclip;
235 }
236 }
237 }
238 else { // cases 9, 10, 11 and 12
239 if (value <= lclip) {
240 return; // bar is not visible
241 }
242 base = getLowerClip();
243 if (value >= uclip) {
244 value = uclip;
245 }
246 }
247
248 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
249 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
250 double transY2 = rangeAxis.valueToJava2D(value, dataArea,
251 yAxisLocation);
252 double rectX = Math.min(transY2, transY1);
253
254 double rectHeight = state.getBarWidth();
255 double rectWidth = Math.abs(transY2 - transY1);
256
257 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
258 rectHeight);
259 Paint seriesPaint = getItemPaint(row, column);
260 g2.setPaint(seriesPaint);
261 g2.fill(bar);
262 if (state.getBarWidth() > 3) {
263 g2.setStroke(getItemStroke(row, column));
264 g2.setPaint(getItemOutlinePaint(row, column));
265 g2.draw(bar);
266 }
267
268 // standard deviation lines
269 double valueDelta = dataset.getStdDevValue(row, column).doubleValue();
270 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
271 + valueDelta, dataArea, yAxisLocation);
272 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
273 - valueDelta, dataArea, yAxisLocation);
274
275 if (this.errorIndicatorPaint != null) {
276 g2.setPaint(this.errorIndicatorPaint);
277 }
278 else {
279 g2.setPaint(getItemOutlinePaint(row, column));
280 }
281 Line2D line = null;
282 line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d,
283 highVal, rectY + rectHeight / 2.0d);
284 g2.draw(line);
285 line = new Line2D.Double(highVal, rectY + rectHeight * 0.25,
286 highVal, rectY + rectHeight * 0.75);
287 g2.draw(line);
288 line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25,
289 lowVal, rectY + rectHeight * 0.75);
290 g2.draw(line);
291
292 CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
293 column);
294 if (generator != null && isItemLabelVisible(row, column)) {
295 drawItemLabel(g2, dataset, row, column, plot, generator, bar,
296 (value < 0.0));
297 }
298
299 // add an item entity, if this information is being collected
300 EntityCollection entities = state.getEntityCollection();
301 if (entities != null) {
302 addItemEntity(entities, dataset, row, column, bar);
303 }
304
305 }
306
307 /**
308 * Draws an item for a plot with a vertical orientation.
309 *
310 * @param g2 the graphics device.
311 * @param state the renderer state.
312 * @param dataArea the data area.
313 * @param plot the plot.
314 * @param domainAxis the domain axis.
315 * @param rangeAxis the range axis.
316 * @param dataset the data.
317 * @param row the row index (zero-based).
318 * @param column the column index (zero-based).
319 */
320 protected void drawVerticalItem(Graphics2D g2,
321 CategoryItemRendererState state,
322 Rectangle2D dataArea,
323 CategoryPlot plot,
324 CategoryAxis domainAxis,
325 ValueAxis rangeAxis,
326 StatisticalCategoryDataset dataset,
327 int row,
328 int column) {
329
330 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
331
332 // BAR X
333 double rectX = domainAxis.getCategoryStart(
334 column, getColumnCount(), dataArea, xAxisLocation
335 );
336
337 int seriesCount = getRowCount();
338 int categoryCount = getColumnCount();
339 if (seriesCount > 1) {
340 double seriesGap = dataArea.getWidth() * getItemMargin()
341 / (categoryCount * (seriesCount - 1));
342 rectX = rectX + row * (state.getBarWidth() + seriesGap);
343 }
344 else {
345 rectX = rectX + row * state.getBarWidth();
346 }
347
348 // BAR Y
349 Number meanValue = dataset.getMeanValue(row, column);
350
351 double value = meanValue.doubleValue();
352 double base = 0.0;
353 double lclip = getLowerClip();
354 double uclip = getUpperClip();
355
356 if (uclip <= 0.0) { // cases 1, 2, 3 and 4
357 if (value >= uclip) {
358 return; // bar is not visible
359 }
360 base = uclip;
361 if (value <= lclip) {
362 value = lclip;
363 }
364 }
365 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
366 if (value >= uclip) {
367 value = uclip;
368 }
369 else {
370 if (value <= lclip) {
371 value = lclip;
372 }
373 }
374 }
375 else { // cases 9, 10, 11 and 12
376 if (value <= lclip) {
377 return; // bar is not visible
378 }
379 base = getLowerClip();
380 if (value >= uclip) {
381 value = uclip;
382 }
383 }
384
385 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
386 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
387 double transY2 = rangeAxis.valueToJava2D(value, dataArea,
388 yAxisLocation);
389 double rectY = Math.min(transY2, transY1);
390
391 double rectWidth = state.getBarWidth();
392 double rectHeight = Math.abs(transY2 - transY1);
393
394 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
395 rectHeight);
396 Paint seriesPaint = getItemPaint(row, column);
397 g2.setPaint(seriesPaint);
398 g2.fill(bar);
399 if (state.getBarWidth() > 3) {
400 g2.setStroke(getItemStroke(row, column));
401 g2.setPaint(getItemOutlinePaint(row, column));
402 g2.draw(bar);
403 }
404
405 // standard deviation lines
406 double valueDelta = dataset.getStdDevValue(row, column).doubleValue();
407 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
408 + valueDelta, dataArea, yAxisLocation);
409 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
410 - valueDelta, dataArea, yAxisLocation);
411
412 if (this.errorIndicatorPaint != null) {
413 g2.setPaint(this.errorIndicatorPaint);
414 }
415 else {
416 g2.setPaint(getItemOutlinePaint(row, column));
417 }
418 Line2D line = null;
419 line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal,
420 rectX + rectWidth / 2.0d, highVal);
421 g2.draw(line);
422 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal,
423 rectX + rectWidth / 2.0d + 5.0d, highVal);
424 g2.draw(line);
425 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal,
426 rectX + rectWidth / 2.0d + 5.0d, lowVal);
427 g2.draw(line);
428
429 CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
430 column);
431 if (generator != null && isItemLabelVisible(row, column)) {
432 drawItemLabel(g2, dataset, row, column, plot, generator, bar,
433 (value < 0.0));
434 }
435
436 // add an item entity, if this information is being collected
437 EntityCollection entities = state.getEntityCollection();
438 if (entities != null) {
439 addItemEntity(entities, dataset, row, column, bar);
440 }
441 }
442
443 /**
444 * Tests this renderer for equality with an arbitrary object.
445 *
446 * @param obj the object (<code>null</code> permitted).
447 *
448 * @return A boolean.
449 */
450 public boolean equals(Object obj) {
451 if (obj == this) {
452 return true;
453 }
454 if (!(obj instanceof StatisticalBarRenderer)) {
455 return false;
456 }
457 if (!super.equals(obj)) {
458 return false;
459 }
460 StatisticalBarRenderer that = (StatisticalBarRenderer) obj;
461 if (!PaintUtilities.equal(this.errorIndicatorPaint,
462 that.errorIndicatorPaint)) {
463 return false;
464 }
465 return true;
466 }
467
468 /**
469 * Provides serialization support.
470 *
471 * @param stream the output stream.
472 *
473 * @throws IOException if there is an I/O error.
474 */
475 private void writeObject(ObjectOutputStream stream) throws IOException {
476 stream.defaultWriteObject();
477 SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
478 }
479
480 /**
481 * Provides serialization support.
482 *
483 * @param stream the input stream.
484 *
485 * @throws IOException if there is an I/O error.
486 * @throws ClassNotFoundException if there is a classpath problem.
487 */
488 private void readObject(ObjectInputStream stream)
489 throws IOException, ClassNotFoundException {
490 stream.defaultReadObject();
491 this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
492 }
493
494 }