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-2007, 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-2007, 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.7 2007/03/09 16:45:06 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:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  56:  * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
  57:  * 09-Mar-2007 : Fixed problem with horizontal rendering (DG);
  58:  * 
  59:  */
  60: 
  61: package org.jfree.chart.renderer.category;
  62: 
  63: import java.awt.BasicStroke;
  64: import java.awt.Color;
  65: import java.awt.Component;
  66: import java.awt.Graphics;
  67: import java.awt.Graphics2D;
  68: import java.awt.Paint;
  69: import java.awt.Shape;
  70: import java.awt.Stroke;
  71: import java.awt.geom.AffineTransform;
  72: import java.awt.geom.Arc2D;
  73: import java.awt.geom.GeneralPath;
  74: import java.awt.geom.Line2D;
  75: import java.awt.geom.Rectangle2D;
  76: import java.io.IOException;
  77: import java.io.ObjectInputStream;
  78: import java.io.ObjectOutputStream;
  79: 
  80: import javax.swing.Icon;
  81: 
  82: import org.jfree.chart.axis.CategoryAxis;
  83: import org.jfree.chart.axis.ValueAxis;
  84: import org.jfree.chart.entity.CategoryItemEntity;
  85: import org.jfree.chart.entity.EntityCollection;
  86: import org.jfree.chart.event.RendererChangeEvent;
  87: import org.jfree.chart.labels.CategoryToolTipGenerator;
  88: import org.jfree.chart.plot.CategoryPlot;
  89: import org.jfree.chart.plot.PlotOrientation;
  90: import org.jfree.data.category.CategoryDataset;
  91: import org.jfree.io.SerialUtilities;
  92: 
  93: /**
  94:  * Renderer for drawing min max plot. This renderer draws all the series under 
  95:  * the same category in the same x position using <code>objectIcon</code> and 
  96:  * a line from the maximum value to the minimum value.
  97:  * <p>
  98:  * For use with the {@link org.jfree.chart.plot.CategoryPlot} class.
  99:  */
 100: public class MinMaxCategoryRenderer extends AbstractCategoryItemRenderer {
 101: 
 102:     /** For serialization. */
 103:     private static final long serialVersionUID = 2935615937671064911L;
 104:     
 105:     /** A flag indicating whether or not lines are drawn between XY points. */
 106:     private boolean plotLines = false;
 107: 
 108:     /** 
 109:      * The paint of the line between the minimum value and the maximum value.
 110:      */
 111:     private transient Paint groupPaint = Color.black;
 112: 
 113:     /** 
 114:      * The stroke of the line between the minimum value and the maximum value.
 115:      */
 116:     private transient Stroke groupStroke = new BasicStroke(1.0f);
 117: 
 118:     /** The icon used to indicate the minimum value.*/
 119:     private transient Icon minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0,
 120:             360, Arc2D.OPEN), null, Color.black);
 121: 
 122:     /** The icon used to indicate the maximum value.*/
 123:     private transient Icon maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0,
 124:             360, Arc2D.OPEN), null, Color.black);
 125: 
 126:     /** The icon used to indicate the values.*/
 127:     private transient Icon objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0),
 128:             false, true);
 129: 
 130:     /** The last category. */
 131:     private int lastCategory = -1;
 132: 
 133:     /** The minimum. */
 134:     private double min;
 135: 
 136:     /** The maximum. */
 137:     private double max;
 138: 
 139:     /**
 140:      * Default constructor.
 141:      */
 142:     public MinMaxCategoryRenderer() {
 143:         super();
 144:     }
 145: 
 146:     /**
 147:      * Gets whether or not lines are drawn between category points.
 148:      *
 149:      * @return boolean true if line will be drawn between sequenced categories,
 150:      *         otherwise false.
 151:      *         
 152:      * @see #setDrawLines(boolean)
 153:      */
 154:     public boolean isDrawLines() {
 155:         return this.plotLines;
 156:     }
 157: 
 158:     /**
 159:      * Sets the flag that controls whether or not lines are drawn to connect
 160:      * the items within a series and sends a {@link RendererChangeEvent} to 
 161:      * all registered listeners.
 162:      *
 163:      * @param draw  the new value of the flag.
 164:      * 
 165:      * @see #isDrawLines()
 166:      */
 167:     public void setDrawLines(boolean draw) {
 168:         if (this.plotLines != draw) {
 169:             this.plotLines = draw;
 170:             this.notifyListeners(new RendererChangeEvent(this));
 171:         }
 172:         
 173:     }
 174: 
 175:     /**
 176:      * Returns the paint used to draw the line between the minimum and maximum
 177:      * value items in each category.
 178:      *
 179:      * @return The paint (never <code>null</code>).
 180:      * 
 181:      * @see #setGroupPaint(Paint)
 182:      */
 183:     public Paint getGroupPaint() {
 184:         return this.groupPaint;
 185:     }
 186: 
 187:     /**
 188:      * Sets the paint used to draw the line between the minimum and maximum
 189:      * value items in each category and sends a {@link RendererChangeEvent} to
 190:      * all registered listeners.
 191:      *
 192:      * @param paint  the paint (<code>null</code> not permitted).
 193:      * 
 194:      * @see #getGroupPaint()
 195:      */
 196:     public void setGroupPaint(Paint paint) {
 197:         if (paint == null) {
 198:             throw new IllegalArgumentException("Null 'paint' argument.");
 199:         }
 200:         this.groupPaint = paint;
 201:         notifyListeners(new RendererChangeEvent(this));
 202:     }
 203: 
 204:     /**
 205:      * Returns the stroke used to draw the line between the minimum and maximum
 206:      * value items in each category.
 207:      *
 208:      * @return The stroke (never <code>null</code>).
 209:      * 
 210:      * @see #setGroupStroke(Stroke)
 211:      */
 212:     public Stroke getGroupStroke() {
 213:         return this.groupStroke;
 214:     }
 215: 
 216:     /**
 217:      * Sets the stroke of the line between the minimum value and the maximum 
 218:      * value.
 219:      *
 220:      * @param groupStroke The new stroke
 221:      */
 222:     public void setGroupStroke(Stroke groupStroke) {
 223:         this.groupStroke = groupStroke;
 224:     }
 225: 
 226:     /**
 227:      * Returns the icon drawn for each data item.
 228:      *
 229:      * @return The icon (never <code>null</code>).
 230:      * 
 231:      * @see #setObjectIcon(Icon)
 232:      */
 233:     public Icon getObjectIcon() {
 234:         return this.objectIcon;
 235:     }
 236: 
 237:     /**
 238:      * Sets the icon drawn for each data item.
 239:      *
 240:      * @param icon  the icon.
 241:      * 
 242:      * @see #getObjectIcon()
 243:      */
 244:     public void setObjectIcon(Icon icon) {
 245:         if (icon == null) {
 246:             throw new IllegalArgumentException("Null 'icon' argument.");
 247:         }
 248:         this.objectIcon = icon;
 249:         notifyListeners(new RendererChangeEvent(this));
 250:     }
 251: 
 252:     /**
 253:      * Returns the icon displayed for the maximum value data item within each
 254:      * category.
 255:      *
 256:      * @return The icon (never <code>null</code>).
 257:      * 
 258:      * @see #setMaxIcon(Icon)
 259:      */
 260:     public Icon getMaxIcon() {
 261:         return this.maxIcon;
 262:     }
 263: 
 264:     /**
 265:      * Sets the icon displayed for the maximum value data item within each
 266:      * category and sends a {@link RendererChangeEvent} to all registered
 267:      * listeners.
 268:      *
 269:      * @param icon  the icon (<code>null</code> not permitted).
 270:      * 
 271:      * @see #getMaxIcon()
 272:      */
 273:     public void setMaxIcon(Icon icon) {
 274:         if (icon == null) {
 275:             throw new IllegalArgumentException("Null 'icon' argument.");
 276:         }
 277:         this.maxIcon = icon;
 278:         notifyListeners(new RendererChangeEvent(this));
 279:     }
 280: 
 281:     /**
 282:      * Returns the icon displayed for the minimum value data item within each
 283:      * category.
 284:      *
 285:      * @return The icon (never <code>null</code>).
 286:      * 
 287:      * @see #setMinIcon(Icon)
 288:      */
 289:     public Icon getMinIcon() {
 290:         return this.minIcon;
 291:     }
 292: 
 293:     /**
 294:      * Sets the icon displayed for the minimum value data item within each
 295:      * category and sends a {@link RendererChangeEvent} to all registered
 296:      * listeners.
 297:      *
 298:      * @param icon  the icon (<code>null</code> not permitted).
 299:      * 
 300:      * @see #getMinIcon()
 301:      */
 302:     public void setMinIcon(Icon icon) {
 303:         if (icon == null) {
 304:             throw new IllegalArgumentException("Null 'icon' argument.");
 305:         }
 306:         this.minIcon = icon;
 307:         notifyListeners(new RendererChangeEvent(this));
 308:     }
 309: 
 310:     /**
 311:      * Draw a single data item.
 312:      *
 313:      * @param g2  the graphics device.
 314:      * @param state  the renderer state.
 315:      * @param dataArea  the area in which the data is drawn.
 316:      * @param plot  the plot.
 317:      * @param domainAxis  the domain axis.
 318:      * @param rangeAxis  the range axis.
 319:      * @param dataset  the dataset.
 320:      * @param row  the row index (zero-based).
 321:      * @param column  the column index (zero-based).
 322:      * @param pass  the pass index.
 323:      */
 324:     public void drawItem(Graphics2D g2, CategoryItemRendererState state,
 325:             Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
 326:             ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
 327:             int pass) {
 328: 
 329:         // first check the number we are plotting...
 330:         Number value = dataset.getValue(row, column);
 331:         if (value != null) {
 332:             // current data point...
 333:             double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 334:                     dataArea, plot.getDomainAxisEdge());
 335:             double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 
 336:                     plot.getRangeAxisEdge());
 337:             g2.setPaint(getItemPaint(row, column));
 338:             g2.setStroke(getItemStroke(row, column));
 339:             Shape shape = null;
 340:             shape = new Rectangle2D.Double(x1 - 4, y1 - 4, 8.0, 8.0);
 341:             
 342:             PlotOrientation orient = plot.getOrientation();
 343:             if (orient == PlotOrientation.VERTICAL) {
 344:                 this.objectIcon.paintIcon(null, g2, (int) x1, (int) y1);
 345:             }
 346:             else {
 347:                 this.objectIcon.paintIcon(null, g2, (int) y1, (int) x1);                
 348:             }
 349:             
 350:             if (this.lastCategory == column) {
 351:                 if (this.min > value.doubleValue()) {
 352:                     this.min = value.doubleValue();
 353:                 }
 354:                 if (this.max < value.doubleValue()) {
 355:                     this.max = value.doubleValue();
 356:                 }
 357:                 
 358:                 // last series, so we are ready to draw the min and max
 359:                 if (dataset.getRowCount() - 1 == row) {
 360:                     g2.setPaint(this.groupPaint);
 361:                     g2.setStroke(this.groupStroke);
 362:                     double minY = rangeAxis.valueToJava2D(this.min, dataArea, 
 363:                             plot.getRangeAxisEdge());
 364:                     double maxY = rangeAxis.valueToJava2D(this.max, dataArea, 
 365:                             plot.getRangeAxisEdge());
 366:                     
 367:                     if (orient == PlotOrientation.VERTICAL) {
 368:                         g2.draw(new Line2D.Double(x1, minY, x1, maxY));
 369:                         this.minIcon.paintIcon(null, g2, (int) x1, (int) minY);
 370:                         this.maxIcon.paintIcon(null, g2, (int) x1, (int) maxY);
 371:                     }
 372:                     else {
 373:                         g2.draw(new Line2D.Double(minY, x1, maxY, x1));
 374:                         this.minIcon.paintIcon(null, g2, (int) minY, (int) x1);
 375:                         this.maxIcon.paintIcon(null, g2, (int) maxY, (int) x1);                        
 376:                     }
 377:                 }
 378:             }
 379:             else {  // reset the min and max
 380:                 this.lastCategory = column;
 381:                 this.min = value.doubleValue();
 382:                 this.max = value.doubleValue();
 383:             }
 384:             
 385:             // connect to the previous point
 386:             if (this.plotLines) {
 387:                 if (column != 0) {
 388:                     Number previousValue = dataset.getValue(row, column - 1);
 389:                     if (previousValue != null) {
 390:                         // previous data point...
 391:                         double previous = previousValue.doubleValue();
 392:                         double x0 = domainAxis.getCategoryMiddle(column - 1, 
 393:                                 getColumnCount(), dataArea,
 394:                                 plot.getDomainAxisEdge());
 395:                         double y0 = rangeAxis.valueToJava2D(previous, dataArea,
 396:                                 plot.getRangeAxisEdge());
 397:                         g2.setPaint(getItemPaint(row, column));
 398:                         g2.setStroke(getItemStroke(row, column));
 399:                         Line2D line;
 400:                         if (orient == PlotOrientation.VERTICAL) {
 401:                             line = new Line2D.Double(x0, y0, x1, y1);
 402:                         }
 403:                         else {
 404:                             line = new Line2D.Double(y0, x0, y1, x1);                            
 405:                         }
 406:                         g2.draw(line);
 407:                     }
 408:                 }
 409:             }
 410: 
 411:             // collect entity and tool tip information...
 412:             if (state.getInfo() != null) {
 413:                 EntityCollection entities = state.getEntityCollection();
 414:                 if (entities != null && shape != null) {
 415:                     String tip = null;
 416:                     CategoryToolTipGenerator tipster = getToolTipGenerator(row,
 417:                             column);
 418:                     if (tipster != null) {
 419:                         tip = tipster.generateToolTip(dataset, row, column);
 420:                     }
 421:                     CategoryItemEntity entity = new CategoryItemEntity(
 422:                             shape, tip, null, dataset, row, 
 423:                             dataset.getColumnKey(column), column);
 424:                     entities.add(entity);
 425:                 }
 426:             }
 427:         }
 428:     }
 429: 
 430:     /**
 431:      * Returns an icon.
 432:      *
 433:      * @param shape  the shape.
 434:      * @param fillPaint  the fill paint.
 435:      * @param outlinePaint  the outline paint.
 436:      *
 437:      * @return The icon.
 438:      */
 439:     private Icon getIcon(Shape shape, final Paint fillPaint, 
 440:                         final Paint outlinePaint) {
 441: 
 442:       final int width = shape.getBounds().width;
 443:       final int height = shape.getBounds().height;
 444:       final GeneralPath path = new GeneralPath(shape);
 445:       return new Icon() {
 446:           public void paintIcon(Component c, Graphics g, int x, int y) {
 447:               Graphics2D g2 = (Graphics2D) g;
 448:               path.transform(AffineTransform.getTranslateInstance(x, y));
 449:               if (fillPaint != null) {
 450:                   g2.setPaint(fillPaint);
 451:                   g2.fill(path);
 452:               }
 453:               if (outlinePaint != null) {
 454:                   g2.setPaint(outlinePaint);
 455:                   g2.draw(path);
 456:               }
 457:               path.transform(AffineTransform.getTranslateInstance(-x, -y));
 458:         }
 459: 
 460:         public int getIconWidth() {
 461:             return width;
 462:         }
 463: 
 464:         public int getIconHeight() {
 465:             return height;
 466:         }
 467: 
 468:       };
 469:     }
 470: 
 471:     /**
 472:      * Returns an icon.
 473:      *
 474:      * @param shape  the shape.
 475:      * @param fill  the fill flag.
 476:      * @param outline  the outline flag.
 477:      *
 478:      * @return The icon.
 479:      */
 480:     private Icon getIcon(Shape shape, final boolean fill, 
 481:                          final boolean outline) {
 482:         final int width = shape.getBounds().width;
 483:         final int height = shape.getBounds().height;
 484:         final GeneralPath path = new GeneralPath(shape);
 485:         return new Icon() {
 486:             public void paintIcon(Component c, Graphics g, int x, int y) {
 487:                 Graphics2D g2 = (Graphics2D) g;
 488:                 path.transform(AffineTransform.getTranslateInstance(x, y));
 489:                 if (fill) {
 490:                     g2.fill(path);
 491:                 }
 492:                 if (outline) {
 493:                     g2.draw(path);
 494:                 }
 495:                 path.transform(AffineTransform.getTranslateInstance(-x, -y));
 496:             }
 497: 
 498:             public int getIconWidth() {
 499:                 return width;
 500:             }
 501: 
 502:             public int getIconHeight() {
 503:                 return height;
 504:             }
 505:         };
 506:     }
 507:     
 508:     /**
 509:      * Provides serialization support.
 510:      *
 511:      * @param stream  the output stream.
 512:      *
 513:      * @throws IOException  if there is an I/O error.
 514:      */
 515:     private void writeObject(ObjectOutputStream stream) throws IOException {
 516:         stream.defaultWriteObject();
 517:         SerialUtilities.writeStroke(this.groupStroke, stream);
 518:         SerialUtilities.writePaint(this.groupPaint, stream);
 519:     }
 520:     
 521:     /**
 522:      * Provides serialization support.
 523:      *
 524:      * @param stream  the input stream.
 525:      *
 526:      * @throws IOException  if there is an I/O error.
 527:      * @throws ClassNotFoundException  if there is a classpath problem.
 528:      */
 529:     private void readObject(ObjectInputStream stream) 
 530:         throws IOException, ClassNotFoundException {
 531:         stream.defaultReadObject();
 532:         this.groupStroke = SerialUtilities.readStroke(stream);
 533:         this.groupPaint = SerialUtilities.readPaint(stream);
 534:           
 535:         this.minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360, 
 536:                 Arc2D.OPEN), null, Color.black);
 537:         this.maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360, 
 538:                 Arc2D.OPEN), null, Color.black);
 539:         this.objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0), false, true);
 540:     }
 541:     
 542: }