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