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-2008, 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-2008, 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:  * Changes:
  39:  * --------
  40:  * 29-May-2002 : Version 1 (TP);
  41:  * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  42:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
  43:  *               CategoryToolTipGenerator interface (DG);
  44:  * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
  45:  * 17-Jan-2003 : Moved plot classes to a separate package (DG);
  46:  * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
  47:  *               method (DG);
  48:  * 30-Jul-2003 : Modified entity constructor (CZ);
  49:  * 08-Sep-2003 : Implemented Serializable (NB);
  50:  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
  51:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  52:  * 17-Nov-2005 : Added change events and argument checks (DG);
  53:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  54:  * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
  55:  * 09-Mar-2007 : Fixed problem with horizontal rendering (DG);
  56:  * 28-Sep-2007 : Added equals() method override (DG);
  57:  *
  58:  */
  59: 
  60: package org.jfree.chart.renderer.category;
  61: 
  62: import java.awt.BasicStroke;
  63: import java.awt.Color;
  64: import java.awt.Component;
  65: import java.awt.Graphics;
  66: import java.awt.Graphics2D;
  67: import java.awt.Paint;
  68: import java.awt.Shape;
  69: import java.awt.Stroke;
  70: import java.awt.geom.AffineTransform;
  71: import java.awt.geom.Arc2D;
  72: import java.awt.geom.GeneralPath;
  73: import java.awt.geom.Line2D;
  74: import java.awt.geom.Rectangle2D;
  75: import java.io.IOException;
  76: import java.io.ObjectInputStream;
  77: import java.io.ObjectOutputStream;
  78: 
  79: import javax.swing.Icon;
  80: 
  81: import org.jfree.chart.axis.CategoryAxis;
  82: import org.jfree.chart.axis.ValueAxis;
  83: import org.jfree.chart.entity.EntityCollection;
  84: import org.jfree.chart.event.RendererChangeEvent;
  85: import org.jfree.chart.plot.CategoryPlot;
  86: import org.jfree.chart.plot.PlotOrientation;
  87: import org.jfree.data.category.CategoryDataset;
  88: import org.jfree.io.SerialUtilities;
  89: import org.jfree.util.PaintUtilities;
  90: 
  91: /**
  92:  * Renderer for drawing min max plot. This renderer draws all the series under
  93:  * the same category in the same x position using <code>objectIcon</code> and
  94:  * a line from the maximum value to the minimum value.
  95:  * <p>
  96:  * For use with the {@link org.jfree.chart.plot.CategoryPlot} class.
  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:             fireChangeEvent();
 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:         fireChangeEvent();
 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 and sends a {@link RendererChangeEvent} to all registered
 217:      * listeners.
 218:      *
 219:      * @param stroke the new stroke (<code>null</code> not permitted).
 220:      */
 221:     public void setGroupStroke(Stroke stroke) {
 222:         if (stroke == null) {
 223:             throw new IllegalArgumentException("Null 'stroke' argument.");
 224:         }
 225:         this.groupStroke = stroke;
 226:         fireChangeEvent();
 227:     }
 228: 
 229:     /**
 230:      * Returns the icon drawn for each data item.
 231:      *
 232:      * @return The icon (never <code>null</code>).
 233:      *
 234:      * @see #setObjectIcon(Icon)
 235:      */
 236:     public Icon getObjectIcon() {
 237:         return this.objectIcon;
 238:     }
 239: 
 240:     /**
 241:      * Sets the icon drawn for each data item and sends a
 242:      * {@link RendererChangeEvent} to all registered listeners.
 243:      *
 244:      * @param icon  the icon.
 245:      *
 246:      * @see #getObjectIcon()
 247:      */
 248:     public void setObjectIcon(Icon icon) {
 249:         if (icon == null) {
 250:             throw new IllegalArgumentException("Null 'icon' argument.");
 251:         }
 252:         this.objectIcon = icon;
 253:         fireChangeEvent();
 254:     }
 255: 
 256:     /**
 257:      * Returns the icon displayed for the maximum value data item within each
 258:      * category.
 259:      *
 260:      * @return The icon (never <code>null</code>).
 261:      *
 262:      * @see #setMaxIcon(Icon)
 263:      */
 264:     public Icon getMaxIcon() {
 265:         return this.maxIcon;
 266:     }
 267: 
 268:     /**
 269:      * Sets the icon displayed for the maximum value data item within each
 270:      * category and sends a {@link RendererChangeEvent} to all registered
 271:      * listeners.
 272:      *
 273:      * @param icon  the icon (<code>null</code> not permitted).
 274:      *
 275:      * @see #getMaxIcon()
 276:      */
 277:     public void setMaxIcon(Icon icon) {
 278:         if (icon == null) {
 279:             throw new IllegalArgumentException("Null 'icon' argument.");
 280:         }
 281:         this.maxIcon = icon;
 282:         fireChangeEvent();
 283:     }
 284: 
 285:     /**
 286:      * Returns the icon displayed for the minimum value data item within each
 287:      * category.
 288:      *
 289:      * @return The icon (never <code>null</code>).
 290:      *
 291:      * @see #setMinIcon(Icon)
 292:      */
 293:     public Icon getMinIcon() {
 294:         return this.minIcon;
 295:     }
 296: 
 297:     /**
 298:      * Sets the icon displayed for the minimum value data item within each
 299:      * category and sends a {@link RendererChangeEvent} to all registered
 300:      * listeners.
 301:      *
 302:      * @param icon  the icon (<code>null</code> not permitted).
 303:      *
 304:      * @see #getMinIcon()
 305:      */
 306:     public void setMinIcon(Icon icon) {
 307:         if (icon == null) {
 308:             throw new IllegalArgumentException("Null 'icon' argument.");
 309:         }
 310:         this.minIcon = icon;
 311:         fireChangeEvent();
 312:     }
 313: 
 314:     /**
 315:      * Draw a single data item.
 316:      *
 317:      * @param g2  the graphics device.
 318:      * @param state  the renderer state.
 319:      * @param dataArea  the area in which the data is drawn.
 320:      * @param plot  the plot.
 321:      * @param domainAxis  the domain axis.
 322:      * @param rangeAxis  the range axis.
 323:      * @param dataset  the dataset.
 324:      * @param row  the row index (zero-based).
 325:      * @param column  the column index (zero-based).
 326:      * @param pass  the pass index.
 327:      */
 328:     public void drawItem(Graphics2D g2, CategoryItemRendererState state,
 329:             Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
 330:             ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
 331:             int pass) {
 332: 
 333:         // first check the number we are plotting...
 334:         Number value = dataset.getValue(row, column);
 335:         if (value != null) {
 336:             // current data point...
 337:             double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
 338:                     dataArea, plot.getDomainAxisEdge());
 339:             double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea,
 340:                     plot.getRangeAxisEdge());
 341:             g2.setPaint(getItemPaint(row, column));
 342:             g2.setStroke(getItemStroke(row, column));
 343:             Shape shape = null;
 344:             shape = new Rectangle2D.Double(x1 - 4, y1 - 4, 8.0, 8.0);
 345: 
 346:             PlotOrientation orient = plot.getOrientation();
 347:             if (orient == PlotOrientation.VERTICAL) {
 348:                 this.objectIcon.paintIcon(null, g2, (int) x1, (int) y1);
 349:             }
 350:             else {
 351:                 this.objectIcon.paintIcon(null, g2, (int) y1, (int) x1);
 352:             }
 353: 
 354:             if (this.lastCategory == column) {
 355:                 if (this.min > value.doubleValue()) {
 356:                     this.min = value.doubleValue();
 357:                 }
 358:                 if (this.max < value.doubleValue()) {
 359:                     this.max = value.doubleValue();
 360:                 }
 361: 
 362:                 // last series, so we are ready to draw the min and max
 363:                 if (dataset.getRowCount() - 1 == row) {
 364:                     g2.setPaint(this.groupPaint);
 365:                     g2.setStroke(this.groupStroke);
 366:                     double minY = rangeAxis.valueToJava2D(this.min, dataArea,
 367:                             plot.getRangeAxisEdge());
 368:                     double maxY = rangeAxis.valueToJava2D(this.max, dataArea,
 369:                             plot.getRangeAxisEdge());
 370: 
 371:                     if (orient == PlotOrientation.VERTICAL) {
 372:                         g2.draw(new Line2D.Double(x1, minY, x1, maxY));
 373:                         this.minIcon.paintIcon(null, g2, (int) x1, (int) minY);
 374:                         this.maxIcon.paintIcon(null, g2, (int) x1, (int) maxY);
 375:                     }
 376:                     else {
 377:                         g2.draw(new Line2D.Double(minY, x1, maxY, x1));
 378:                         this.minIcon.paintIcon(null, g2, (int) minY, (int) x1);
 379:                         this.maxIcon.paintIcon(null, g2, (int) maxY, (int) x1);
 380:                     }
 381:                 }
 382:             }
 383:             else {  // reset the min and max
 384:                 this.lastCategory = column;
 385:                 this.min = value.doubleValue();
 386:                 this.max = value.doubleValue();
 387:             }
 388: 
 389:             // connect to the previous point
 390:             if (this.plotLines) {
 391:                 if (column != 0) {
 392:                     Number previousValue = dataset.getValue(row, column - 1);
 393:                     if (previousValue != null) {
 394:                         // previous data point...
 395:                         double previous = previousValue.doubleValue();
 396:                         double x0 = domainAxis.getCategoryMiddle(column - 1,
 397:                                 getColumnCount(), dataArea,
 398:                                 plot.getDomainAxisEdge());
 399:                         double y0 = rangeAxis.valueToJava2D(previous, dataArea,
 400:                                 plot.getRangeAxisEdge());
 401:                         g2.setPaint(getItemPaint(row, column));
 402:                         g2.setStroke(getItemStroke(row, column));
 403:                         Line2D line;
 404:                         if (orient == PlotOrientation.VERTICAL) {
 405:                             line = new Line2D.Double(x0, y0, x1, y1);
 406:                         }
 407:                         else {
 408:                             line = new Line2D.Double(y0, x0, y1, x1);
 409:                         }
 410:                         g2.draw(line);
 411:                     }
 412:                 }
 413:             }
 414: 
 415:             // add an item entity, if this information is being collected
 416:             EntityCollection entities = state.getEntityCollection();
 417:             if (entities != null && shape != null) {
 418:                 addItemEntity(entities, dataset, row, column, shape);
 419:             }
 420:         }
 421:     }
 422: 
 423:     /**
 424:      * Tests this instance for equality with an arbitrary object.  The icon
 425:      * fields are NOT included in the test, so this implementation is a little
 426:      * weak.
 427:      *
 428:      * @param obj  the object (<code>null</code> permitted).
 429:      *
 430:      * @return A boolean.
 431:      *
 432:      * @since 1.0.7
 433:      */
 434:     public boolean equals(Object obj) {
 435:         if (obj == this) {
 436:             return true;
 437:         }
 438:         if (!(obj instanceof MinMaxCategoryRenderer)) {
 439:             return false;
 440:         }
 441:         MinMaxCategoryRenderer that = (MinMaxCategoryRenderer) obj;
 442:         if (this.plotLines != that.plotLines) {
 443:             return false;
 444:         }
 445:         if (!PaintUtilities.equal(this.groupPaint, that.groupPaint)) {
 446:             return false;
 447:         }
 448:         if (!this.groupStroke.equals(that.groupStroke)) {
 449:             return false;
 450:         }
 451:         return super.equals(obj);
 452:     }
 453: 
 454:     /**
 455:      * Returns an icon.
 456:      *
 457:      * @param shape  the shape.
 458:      * @param fillPaint  the fill paint.
 459:      * @param outlinePaint  the outline paint.
 460:      *
 461:      * @return The icon.
 462:      */
 463:     private Icon getIcon(Shape shape, final Paint fillPaint,
 464:                         final Paint outlinePaint) {
 465: 
 466:       final int width = shape.getBounds().width;
 467:       final int height = shape.getBounds().height;
 468:       final GeneralPath path = new GeneralPath(shape);
 469:       return new Icon() {
 470:           public void paintIcon(Component c, Graphics g, int x, int y) {
 471:               Graphics2D g2 = (Graphics2D) g;
 472:               path.transform(AffineTransform.getTranslateInstance(x, y));
 473:               if (fillPaint != null) {
 474:                   g2.setPaint(fillPaint);
 475:                   g2.fill(path);
 476:               }
 477:               if (outlinePaint != null) {
 478:                   g2.setPaint(outlinePaint);
 479:                   g2.draw(path);
 480:               }
 481:               path.transform(AffineTransform.getTranslateInstance(-x, -y));
 482:         }
 483: 
 484:         public int getIconWidth() {
 485:             return width;
 486:         }
 487: 
 488:         public int getIconHeight() {
 489:             return height;
 490:         }
 491: 
 492:       };
 493:     }
 494: 
 495:     /**
 496:      * Returns an icon from a shape.
 497:      *
 498:      * @param shape  the shape.
 499:      * @param fill  the fill flag.
 500:      * @param outline  the outline flag.
 501:      *
 502:      * @return The icon.
 503:      */
 504:     private Icon getIcon(Shape shape, final boolean fill,
 505:             final boolean outline) {
 506:         final int width = shape.getBounds().width;
 507:         final int height = shape.getBounds().height;
 508:         final GeneralPath path = new GeneralPath(shape);
 509:         return new Icon() {
 510:             public void paintIcon(Component c, Graphics g, int x, int y) {
 511:                 Graphics2D g2 = (Graphics2D) g;
 512:                 path.transform(AffineTransform.getTranslateInstance(x, y));
 513:                 if (fill) {
 514:                     g2.fill(path);
 515:                 }
 516:                 if (outline) {
 517:                     g2.draw(path);
 518:                 }
 519:                 path.transform(AffineTransform.getTranslateInstance(-x, -y));
 520:             }
 521: 
 522:             public int getIconWidth() {
 523:                 return width;
 524:             }
 525: 
 526:             public int getIconHeight() {
 527:                 return height;
 528:             }
 529:         };
 530:     }
 531: 
 532:     /**
 533:      * Provides serialization support.
 534:      *
 535:      * @param stream  the output stream.
 536:      *
 537:      * @throws IOException  if there is an I/O error.
 538:      */
 539:     private void writeObject(ObjectOutputStream stream) throws IOException {
 540:         stream.defaultWriteObject();
 541:         SerialUtilities.writeStroke(this.groupStroke, stream);
 542:         SerialUtilities.writePaint(this.groupPaint, stream);
 543:     }
 544: 
 545:     /**
 546:      * Provides serialization support.
 547:      *
 548:      * @param stream  the input stream.
 549:      *
 550:      * @throws IOException  if there is an I/O error.
 551:      * @throws ClassNotFoundException  if there is a classpath problem.
 552:      */
 553:     private void readObject(ObjectInputStream stream)
 554:         throws IOException, ClassNotFoundException {
 555:         stream.defaultReadObject();
 556:         this.groupStroke = SerialUtilities.readStroke(stream);
 557:         this.groupPaint = SerialUtilities.readPaint(stream);
 558: 
 559:         this.minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360,
 560:                 Arc2D.OPEN), null, Color.black);
 561:         this.maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360,
 562:                 Arc2D.OPEN), null, Color.black);
 563:         this.objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0), false, true);
 564:     }
 565: 
 566: }