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