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