Frames | No Frames |
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: }