Source for org.jfree.chart.renderer.category.MinMaxCategoryRenderer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ---------------------------
  28:  * MinMaxCategoryRenderer.java
  29:  * ---------------------------
  30:  * (C) Copyright 2002-2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  Tomer Peretz;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Christian W. Zuckschwerdt;
  35:  *                   Nicolas Brodu (for Astrium and EADS Corporate Research 
  36:  *                   Center);
  37:  *
  38:  * $Id: MinMaxCategoryRenderer.java,v 1.6.2.5 2005/12/02 10:05:57 mungady Exp $
  39:  *
  40:  * Changes:
  41:  * --------
  42:  * 29-May-2002 : Version 1 (TP);
  43:  * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  44:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
  45:  *               CategoryToolTipGenerator interface (DG);
  46:  * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
  47:  * 17-Jan-2003 : Moved plot classes to a separate package (DG);
  48:  * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem() 
  49:  *               method (DG);
  50:  * 30-Jul-2003 : Modified entity constructor (CZ);
  51:  * 08-Sep-2003 : Implemented Serializable (NB);
  52:  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
  53:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  54:  * 17-Nov-2005 : Added change events and argument checks (DG);
  55:  * 
  56:  */
  57: 
  58: package org.jfree.chart.renderer.category;
  59: 
  60: import java.awt.BasicStroke;
  61: import java.awt.Color;
  62: import java.awt.Component;
  63: import java.awt.Graphics;
  64: import java.awt.Graphics2D;
  65: import java.awt.Paint;
  66: import java.awt.Shape;
  67: import java.awt.Stroke;
  68: import java.awt.geom.AffineTransform;
  69: import java.awt.geom.Arc2D;
  70: import java.awt.geom.GeneralPath;
  71: import java.awt.geom.Line2D;
  72: import java.awt.geom.Rectangle2D;
  73: import java.io.IOException;
  74: import java.io.ObjectInputStream;
  75: import java.io.ObjectOutputStream;
  76: 
  77: import javax.swing.Icon;
  78: 
  79: import org.jfree.chart.axis.CategoryAxis;
  80: import org.jfree.chart.axis.ValueAxis;
  81: import org.jfree.chart.entity.CategoryItemEntity;
  82: import org.jfree.chart.entity.EntityCollection;
  83: import org.jfree.chart.event.RendererChangeEvent;
  84: import org.jfree.chart.labels.CategoryToolTipGenerator;
  85: import org.jfree.chart.plot.CategoryPlot;
  86: import org.jfree.data.category.CategoryDataset;
  87: import org.jfree.io.SerialUtilities;
  88: 
  89: /**
  90:  * Renderer for drawing min max plot. This renderer draws all the series under 
  91:  * the same category in the same x position using <code>objectIcon</code> and 
  92:  * a line from the maximum value to the minimum value.
  93:  * <p>
  94:  * For use with the {@link org.jfree.chart.plot.CategoryPlot} class.
  95:  *
  96:  * @author Tomer Peretz
  97:  */
  98: public class MinMaxCategoryRenderer extends AbstractCategoryItemRenderer {
  99: 
 100:     /** For serialization. */
 101:     private static final long serialVersionUID = 2935615937671064911L;
 102:     
 103:     /** A flag indicating whether or not lines are drawn between XY points. */
 104:     private boolean plotLines = false;
 105: 
 106:     /** 
 107:      * The paint of the line between the minimum value and the maximum value.
 108:      */
 109:     private transient Paint groupPaint = Color.black;
 110: 
 111:     /** 
 112:      * The stroke of the line between the minimum value and the maximum value.
 113:      */
 114:     private transient Stroke groupStroke = new BasicStroke(1.0f);
 115: 
 116:     /** The icon used to indicate the minimum value.*/
 117:     private transient Icon minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0,
 118:             360, Arc2D.OPEN), null, Color.black);
 119: 
 120:     /** The icon used to indicate the maximum value.*/
 121:     private transient Icon maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0,
 122:             360, Arc2D.OPEN), null, Color.black);
 123: 
 124:     /** The icon used to indicate the values.*/
 125:     private transient Icon objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0),
 126:             false, true);
 127: 
 128:     /** The last category. */
 129:     private int lastCategory = -1;
 130: 
 131:     /** The minimum. */
 132:     private double min;
 133: 
 134:     /** The maximum. */
 135:     private double max;
 136: 
 137:     /**
 138:      * Default constructor.
 139:      */
 140:     public MinMaxCategoryRenderer() {
 141:         super();
 142:     }
 143: 
 144:     /**
 145:      * Gets whether or not lines are drawn between category points.
 146:      *
 147:      * @return boolean true if line will be drawn between sequenced categories,
 148:      *         otherwise false.
 149:      *         
 150:      * @see #setDrawLines(boolean)
 151:      */
 152:     public boolean isDrawLines() {
 153:         return this.plotLines;
 154:     }
 155: 
 156:     /**
 157:      * Sets the flag that controls whether or not lines are drawn to connect
 158:      * the items within a series and sends a {@link RendererChangeEvent} to 
 159:      * all registered listeners.
 160:      *
 161:      * @param draw  the new value of the flag.
 162:      * 
 163:      * @see #isDrawLines()
 164:      */
 165:     public void setDrawLines(boolean draw) {
 166:         if (this.plotLines != draw) {
 167:             this.plotLines = draw;
 168:             this.notifyListeners(new RendererChangeEvent(this));
 169:         }
 170:         
 171:     }
 172: 
 173:     /**
 174:      * Returns the paint used to draw the line between the minimum and maximum
 175:      * value items in each category.
 176:      *
 177:      * @return The paint (never <code>null</code>).
 178:      * 
 179:      * @see #setGroupPaint(Paint)
 180:      */
 181:     public Paint getGroupPaint() {
 182:         return this.groupPaint;
 183:     }
 184: 
 185:     /**
 186:      * Sets the paint used to draw the line between the minimum and maximum
 187:      * value items in each category and sends a {@link RendererChangeEvent} to
 188:      * all registered listeners.
 189:      *
 190:      * @param paint  the paint (<code>null</code> not permitted).
 191:      * 
 192:      * @see #getGroupPaint()
 193:      */
 194:     public void setGroupPaint(Paint paint) {
 195:         if (paint == null) {
 196:             throw new IllegalArgumentException("Null 'paint' argument.");
 197:         }
 198:         this.groupPaint = paint;
 199:         notifyListeners(new RendererChangeEvent(this));
 200:     }
 201: 
 202:     /**
 203:      * Returns the stroke used to draw the line between the minimum and maximum
 204:      * value items in each category.
 205:      *
 206:      * @return The stroke (never <code>null</code>).
 207:      * 
 208:      * @see #setGroupStroke(Stroke)
 209:      */
 210:     public Stroke getGroupStroke() {
 211:         return this.groupStroke;
 212:     }
 213: 
 214:     /**
 215:      * Sets the stroke of the line between the minimum value and the maximum 
 216:      * value.
 217:      *
 218:      * @param groupStroke The new stroke
 219:      */
 220:     public void setGroupStroke(Stroke groupStroke) {
 221:         this.groupStroke = groupStroke;
 222:     }
 223: 
 224:     /**
 225:      * Returns the icon drawn for each data item.
 226:      *
 227:      * @return The icon (never <code>null</code>).
 228:      * 
 229:      * @see #setObjectIcon(Icon)
 230:      */
 231:     public Icon getObjectIcon() {
 232:         return this.objectIcon;
 233:     }
 234: 
 235:     /**
 236:      * Sets the icon drawn for each data item.
 237:      *
 238:      * @param icon  the icon.
 239:      * 
 240:      * @see #getObjectIcon()
 241:      */
 242:     public void setObjectIcon(Icon icon) {
 243:         if (icon == null) {
 244:             throw new IllegalArgumentException("Null 'icon' argument.");
 245:         }
 246:         this.objectIcon = icon;
 247:         notifyListeners(new RendererChangeEvent(this));
 248:     }
 249: 
 250:     /**
 251:      * Returns the icon displayed for the maximum value data item within each
 252:      * category.
 253:      *
 254:      * @return The icon (never <code>null</code>).
 255:      * 
 256:      * @see #setMaxIcon(Icon)
 257:      */
 258:     public Icon getMaxIcon() {
 259:         return this.maxIcon;
 260:     }
 261: 
 262:     /**
 263:      * Sets the icon displayed for the maximum value data item within each
 264:      * category and sends a {@link RendererChangeEvent} to all registered
 265:      * listeners.
 266:      *
 267:      * @param icon  the icon (<code>null</code> not permitted).
 268:      * 
 269:      * @see #getMaxIcon()
 270:      */
 271:     public void setMaxIcon(Icon icon) {
 272:         if (icon == null) {
 273:             throw new IllegalArgumentException("Null 'icon' argument.");
 274:         }
 275:         this.maxIcon = icon;
 276:         notifyListeners(new RendererChangeEvent(this));
 277:     }
 278: 
 279:     /**
 280:      * Returns the icon displayed for the minimum value data item within each
 281:      * category.
 282:      *
 283:      * @return The icon (never <code>null</code>).
 284:      * 
 285:      * @see #setMinIcon(Icon)
 286:      */
 287:     public Icon getMinIcon() {
 288:         return this.minIcon;
 289:     }
 290: 
 291:     /**
 292:      * Sets the icon displayed for the minimum value data item within each
 293:      * category and sends a {@link RendererChangeEvent} to all registered
 294:      * listeners.
 295:      *
 296:      * @param icon  the icon (<code>null</code> not permitted).
 297:      * 
 298:      * @see #getMinIcon()
 299:      */
 300:     public void setMinIcon(Icon icon) {
 301:         if (icon == null) {
 302:             throw new IllegalArgumentException("Null 'icon' argument.");
 303:         }
 304:         this.minIcon = icon;
 305:         notifyListeners(new RendererChangeEvent(this));
 306:     }
 307: 
 308:     /**
 309:      * Draw a single data item.
 310:      *
 311:      * @param g2  the graphics device.
 312:      * @param state  the renderer state.
 313:      * @param dataArea  the area in which the data is drawn.
 314:      * @param plot  the plot.
 315:      * @param domainAxis  the domain axis.
 316:      * @param rangeAxis  the range axis.
 317:      * @param dataset  the dataset.
 318:      * @param row  the row index (zero-based).
 319:      * @param column  the column index (zero-based).
 320:      * @param pass  the pass index.
 321:      */
 322:     public void drawItem(Graphics2D g2, CategoryItemRendererState state,
 323:             Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
 324:             ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
 325:             int pass) {
 326: 
 327:         // first check the number we are plotting...
 328:         Number value = dataset.getValue(row, column);
 329:         if (value != null) {
 330:             // current data point...
 331:             double x1 = domainAxis.getCategoryMiddle(
 332:                 column, getColumnCount(), dataArea, plot.getDomainAxisEdge());
 333:             double y1 = rangeAxis.valueToJava2D(
 334:                 value.doubleValue(), dataArea, plot.getRangeAxisEdge());
 335:             g2.setPaint(getItemPaint(row, column));
 336:             g2.setStroke(getItemStroke(row, column));
 337:             Shape shape = null;
 338:             shape = new Rectangle2D.Double(x1 - 4, y1 - 4, 8.0, 8.0);
 339:             this.objectIcon.paintIcon(null, g2, (int) x1, (int) y1);
 340:             if (this.lastCategory == column) {
 341:                 if (this.min > value.doubleValue()) {
 342:                     this.min = value.doubleValue();
 343:                 }
 344:                 if (this.max < value.doubleValue()) {
 345:                     this.max = value.doubleValue();
 346:                 }
 347:                 if (dataset.getRowCount() - 1 == row) {
 348:                     g2.setPaint(this.groupPaint);
 349:                     g2.setStroke(this.groupStroke);
 350:                     double minY = rangeAxis.valueToJava2D(this.min, dataArea, 
 351:                             plot.getRangeAxisEdge());
 352:                     double maxY = rangeAxis.valueToJava2D(this.max, dataArea, 
 353:                             plot.getRangeAxisEdge());
 354:                     g2.draw(new Line2D.Double(x1, minY, x1, maxY));
 355:                     this.minIcon.paintIcon(null, g2, (int) x1, (int) minY);
 356:                     this.maxIcon.paintIcon(null, g2, (int) x1, (int) maxY);
 357:                 }
 358:             }
 359:             else {  // reset the min and max
 360:                 this.lastCategory = column;
 361:                 this.min = value.doubleValue();
 362:                 this.max = value.doubleValue();
 363:             }
 364:             // connect to the previous point
 365:             if (this.plotLines) {
 366:                 if (column != 0) {
 367:                     Number previousValue = dataset.getValue(row, column - 1);
 368:                     if (previousValue != null) {
 369:                         // previous data point...
 370:                         double previous = previousValue.doubleValue();
 371:                         double x0 = domainAxis.getCategoryMiddle(
 372:                             column - 1, getColumnCount(), dataArea,
 373:                             plot.getDomainAxisEdge());
 374:                         double y0 = rangeAxis.valueToJava2D(
 375:                             previous, dataArea, plot.getRangeAxisEdge());
 376:                         g2.setPaint(getItemPaint(row, column));
 377:                         g2.setStroke(getItemStroke(row, column));
 378:                         Line2D line = new Line2D.Double(x0, y0, x1, y1);
 379:                         g2.draw(line);
 380:                     }
 381:                 }
 382:             }
 383: 
 384:             // collect entity and tool tip information...
 385:             if (state.getInfo() != null) {
 386:                 EntityCollection entities = state.getEntityCollection();
 387:                 if (entities != null && shape != null) {
 388:                     String tip = null;
 389:                     CategoryToolTipGenerator tipster 
 390:                         = getToolTipGenerator(row, column);
 391:                     if (tipster != null) {
 392:                         tip = tipster.generateToolTip(dataset, row, column);
 393:                     }
 394:                     CategoryItemEntity entity = new CategoryItemEntity(
 395:                         shape, tip, null, dataset, row, 
 396:                         dataset.getColumnKey(column), column);
 397:                     entities.add(entity);
 398:                 }
 399:             }
 400:         }
 401:     }
 402: 
 403:     /**
 404:      * Returns an icon.
 405:      *
 406:      * @param shape  the shape.
 407:      * @param fillPaint  the fill paint.
 408:      * @param outlinePaint  the outline paint.
 409:      *
 410:      * @return The icon.
 411:      */
 412:     private Icon getIcon(Shape shape, final Paint fillPaint, 
 413:                         final Paint outlinePaint) {
 414: 
 415:       final int width = shape.getBounds().width;
 416:       final int height = shape.getBounds().height;
 417:       final GeneralPath path = new GeneralPath(shape);
 418:       return new Icon() {
 419:           public void paintIcon(Component c, Graphics g, int x, int y) {
 420:               Graphics2D g2 = (Graphics2D) g;
 421:               path.transform(AffineTransform.getTranslateInstance(x, y));
 422:               if (fillPaint != null) {
 423:                   g2.setPaint(fillPaint);
 424:                   g2.fill(path);
 425:               }
 426:               if (outlinePaint != null) {
 427:                   g2.setPaint(outlinePaint);
 428:                   g2.draw(path);
 429:               }
 430:               path.transform(AffineTransform.getTranslateInstance(-x, -y));
 431:         }
 432: 
 433:         public int getIconWidth() {
 434:             return width;
 435:         }
 436: 
 437:         public int getIconHeight() {
 438:             return height;
 439:         }
 440: 
 441:       };
 442:     }
 443: 
 444:     /**
 445:      * Returns an icon.
 446:      *
 447:      * @param shape  the shape.
 448:      * @param fill  the fill flag.
 449:      * @param outline  the outline flag.
 450:      *
 451:      * @return The icon.
 452:      */
 453:     private Icon getIcon(Shape shape, final boolean fill, 
 454:                          final boolean outline) {
 455:         final int width = shape.getBounds().width;
 456:         final int height = shape.getBounds().height;
 457:         final GeneralPath path = new GeneralPath(shape);
 458:         return new Icon() {
 459:             public void paintIcon(Component c, Graphics g, int x, int y) {
 460:                 Graphics2D g2 = (Graphics2D) g;
 461:                 path.transform(AffineTransform.getTranslateInstance(x, y));
 462:                 if (fill) {
 463:                     g2.fill(path);
 464:                 }
 465:                 if (outline) {
 466:                     g2.draw(path);
 467:                 }
 468:                 path.transform(AffineTransform.getTranslateInstance(-x, -y));
 469:             }
 470: 
 471:             public int getIconWidth() {
 472:                 return width;
 473:             }
 474: 
 475:             public int getIconHeight() {
 476:                 return height;
 477:             }
 478:         };
 479:     }
 480:     
 481:     /**
 482:      * Provides serialization support.
 483:      *
 484:      * @param stream  the output stream.
 485:      *
 486:      * @throws IOException  if there is an I/O error.
 487:      */
 488:     private void writeObject(ObjectOutputStream stream) throws IOException {
 489:         stream.defaultWriteObject();
 490:         SerialUtilities.writeStroke(this.groupStroke, stream);
 491:         SerialUtilities.writePaint(this.groupPaint, stream);
 492:     }
 493:     
 494:     /**
 495:      * Provides serialization support.
 496:      *
 497:      * @param stream  the input stream.
 498:      *
 499:      * @throws IOException  if there is an I/O error.
 500:      * @throws ClassNotFoundException  if there is a classpath problem.
 501:      */
 502:     private void readObject(ObjectInputStream stream) 
 503:         throws IOException, ClassNotFoundException {
 504:         stream.defaultReadObject();
 505:         this.groupStroke = SerialUtilities.readStroke(stream);
 506:         this.groupPaint = SerialUtilities.readPaint(stream);
 507:           
 508:         this.minIcon = getIcon(
 509:             new Arc2D.Double(-4, -4, 8, 8, 0, 360, Arc2D.OPEN), null, 
 510:             Color.black);
 511:         this.maxIcon = getIcon(
 512:             new Arc2D.Double(-4, -4, 8, 8, 0, 360, Arc2D.OPEN), null, 
 513:             Color.black);
 514:         this.objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0), false, true);
 515:     }
 516:     
 517: }