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

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, 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:  * LevelRenderer.java
  29:  * ------------------
  30:  * (C) Copyright 2004, 2006, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: LevelRenderer.java,v 1.7.2.3 2006/01/23 09:53:58 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 09-Jan-2004 : Version 1 (DG);
  40:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  41:  * 20-Apr-2005 : Renamed CategoryLabelGenerator 
  42:  *               --> CategoryItemLabelGenerator (DG);
  43:  * ------------- JFREECHART 1.0.0 ---------------------------------------------
  44:  * 23-Jan-2006 : Renamed getMaxItemWidth() --> getMaximumItemWidth() (DG);
  45:  * 
  46:  */
  47: 
  48: package org.jfree.chart.renderer.category;
  49: 
  50: import java.awt.Graphics2D;
  51: import java.awt.Paint;
  52: import java.awt.Stroke;
  53: import java.awt.geom.Line2D;
  54: import java.awt.geom.Rectangle2D;
  55: import java.io.Serializable;
  56: 
  57: import org.jfree.chart.axis.CategoryAxis;
  58: import org.jfree.chart.axis.ValueAxis;
  59: import org.jfree.chart.entity.CategoryItemEntity;
  60: import org.jfree.chart.entity.EntityCollection;
  61: import org.jfree.chart.event.RendererChangeEvent;
  62: import org.jfree.chart.labels.CategoryItemLabelGenerator;
  63: import org.jfree.chart.labels.CategoryToolTipGenerator;
  64: import org.jfree.chart.plot.CategoryPlot;
  65: import org.jfree.chart.plot.PlotOrientation;
  66: import org.jfree.chart.plot.PlotRenderingInfo;
  67: import org.jfree.data.category.CategoryDataset;
  68: import org.jfree.ui.RectangleEdge;
  69: import org.jfree.util.PublicCloneable;
  70: 
  71: /**
  72:  * A {@link CategoryItemRenderer} that draws individual data items as 
  73:  * horizontal lines, spaced in the same way as bars in a bar chart.
  74:  */
  75: public class LevelRenderer extends AbstractCategoryItemRenderer 
  76:                            implements Cloneable, PublicCloneable, Serializable {
  77: 
  78:     /** For serialization. */
  79:     private static final long serialVersionUID = -8204856624355025117L;
  80:     
  81:     /** The default item margin percentage. */
  82:     public static final double DEFAULT_ITEM_MARGIN = 0.20;
  83: 
  84:     /** The margin between items within a category. */
  85:     private double itemMargin;
  86: 
  87:     /** The maximum item width as a percentage of the available space. */
  88:     private double maxItemWidth;
  89:     
  90:     /**
  91:      * Creates a new renderer with default settings.
  92:      */
  93:     public LevelRenderer() {
  94:         super();
  95:         this.itemMargin = DEFAULT_ITEM_MARGIN;
  96:         this.maxItemWidth = 1.0;  // 100 percent, so it will not apply unless 
  97:                                   // changed
  98:     }
  99: 
 100:     /**
 101:      * Returns the item margin.
 102:      *
 103:      * @return The margin.
 104:      */
 105:     public double getItemMargin() {
 106:         return this.itemMargin;
 107:     }
 108: 
 109:     /**
 110:      * Sets the item margin.  The value is expressed as a percentage of the 
 111:      * available width for plotting all the bars, with the resulting amount to 
 112:      * be distributed between all the bars evenly.
 113:      *
 114:      * @param percent  the new margin.
 115:      */
 116:     public void setItemMargin(double percent) {
 117:         this.itemMargin = percent;
 118:         notifyListeners(new RendererChangeEvent(this));
 119:     }
 120:     
 121:     /**
 122:      * Returns the maximum width, as a percentage of the available drawing 
 123:      * space.
 124:      * 
 125:      * @return The maximum width.
 126:      * 
 127:      * @deprecated Use {@link #getMaximumItemWidth()} instead.
 128:      */
 129:     public double getMaxItemWidth() {
 130:         return this.maxItemWidth;
 131:     }
 132:     
 133:     /**
 134:      * Sets the maximum item width, which is specified as a percentage of the 
 135:      * available space for all items, and sends a {@link RendererChangeEvent} 
 136:      * to all registered listeners.
 137:      * 
 138:      * @param percent  the percent.
 139:      * 
 140:      * @deprecated Use {@link #setMaximumItemWidth(double)} instead.
 141:      */
 142:     public void setMaxItemWidth(double percent) {
 143:         this.maxItemWidth = percent;
 144:         notifyListeners(new RendererChangeEvent(this));
 145:     }
 146: 
 147:     /**
 148:      * Returns the maximum width, as a percentage of the available drawing 
 149:      * space.
 150:      * 
 151:      * @return The maximum width.
 152:      */
 153:     public double getMaximumItemWidth() {
 154:         return getMaxItemWidth();
 155:     }
 156:     
 157:     /**
 158:      * Sets the maximum item width, which is specified as a percentage of the 
 159:      * available space for all items, and sends a {@link RendererChangeEvent} 
 160:      * to all registered listeners.
 161:      * 
 162:      * @param percent  the percent.
 163:      */
 164:     public void setMaximumItemWidth(double percent) {
 165:         setMaxItemWidth(percent);
 166:     }
 167: 
 168:     /**
 169:      * Initialises the renderer and returns a state object that will be passed 
 170:      * to subsequent calls to the drawItem method.
 171:      * <p>
 172:      * This method gets called once at the start of the process of drawing a 
 173:      * chart.
 174:      *
 175:      * @param g2  the graphics device.
 176:      * @param dataArea  the area in which the data is to be plotted.
 177:      * @param plot  the plot.
 178:      * @param rendererIndex  the renderer index.
 179:      * @param info  collects chart rendering information for return to caller.
 180:      * 
 181:      * @return The renderer state.
 182:      *
 183:      */
 184:     public CategoryItemRendererState initialise(Graphics2D g2,
 185:                                                 Rectangle2D dataArea,
 186:                                                 CategoryPlot plot,
 187:                                                 int rendererIndex,
 188:                                                 PlotRenderingInfo info) {
 189: 
 190:         CategoryItemRendererState state = super.initialise(g2, dataArea, plot, 
 191:                 rendererIndex, info);
 192:         calculateItemWidth(plot, dataArea, rendererIndex, state);
 193:         return state;
 194:         
 195:     }
 196:     
 197:     /**
 198:      * Calculates the bar width and stores it in the renderer state.
 199:      * 
 200:      * @param plot  the plot.
 201:      * @param dataArea  the data area.
 202:      * @param rendererIndex  the renderer index.
 203:      * @param state  the renderer state.
 204:      */
 205:     protected void calculateItemWidth(CategoryPlot plot, 
 206:                                       Rectangle2D dataArea, 
 207:                                       int rendererIndex,
 208:                                       CategoryItemRendererState state) {
 209:                                          
 210:         CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
 211:         CategoryDataset dataset = plot.getDataset(rendererIndex);
 212:         if (dataset != null) {
 213:             int columns = dataset.getColumnCount();
 214:             int rows = dataset.getRowCount();
 215:             double space = 0.0;
 216:             PlotOrientation orientation = plot.getOrientation();
 217:             if (orientation == PlotOrientation.HORIZONTAL) {
 218:                 space = dataArea.getHeight();
 219:             }
 220:             else if (orientation == PlotOrientation.VERTICAL) {
 221:                 space = dataArea.getWidth();
 222:             }
 223:             double maxWidth = space * getMaxItemWidth();
 224:             double categoryMargin = 0.0;
 225:             double currentItemMargin = 0.0;
 226:             if (columns > 1) {
 227:                 categoryMargin = domainAxis.getCategoryMargin();
 228:             }
 229:             if (rows > 1) {
 230:                 currentItemMargin = getItemMargin();
 231:             }
 232:             double used = space * (1 - domainAxis.getLowerMargin() 
 233:                                      - domainAxis.getUpperMargin()
 234:                                      - categoryMargin - currentItemMargin);
 235:             if ((rows * columns) > 0) {
 236:                 state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
 237:             }
 238:             else {
 239:                 state.setBarWidth(Math.min(used, maxWidth));
 240:             }
 241:         }
 242:     }
 243: 
 244:     /**
 245:      * Calculates the coordinate of the first "side" of a bar.  This will be 
 246:      * the minimum x-coordinate for a vertical bar, and the minimum 
 247:      * y-coordinate for a horizontal bar.
 248:      * 
 249:      * @param plot  the plot.
 250:      * @param orientation  the plot orientation.
 251:      * @param dataArea  the data area.
 252:      * @param domainAxis  the domain axis.
 253:      * @param state  the renderer state (has the bar width precalculated).
 254:      * @param row  the row index.
 255:      * @param column  the column index.
 256:      * 
 257:      * @return The coordinate.
 258:      */
 259:     protected double calculateBarW0(CategoryPlot plot, 
 260:                                     PlotOrientation orientation, 
 261:                                     Rectangle2D dataArea,
 262:                                     CategoryAxis domainAxis,
 263:                                     CategoryItemRendererState state,
 264:                                     int row,
 265:                                     int column) {
 266:         // calculate bar width...
 267:         double space = 0.0;
 268:         if (orientation == PlotOrientation.HORIZONTAL) {
 269:             space = dataArea.getHeight();
 270:         }
 271:         else {
 272:             space = dataArea.getWidth();
 273:         }
 274:         double barW0 = domainAxis.getCategoryStart(column, getColumnCount(), 
 275:                 dataArea, plot.getDomainAxisEdge());
 276:         int seriesCount = getRowCount();
 277:         int categoryCount = getColumnCount();
 278:         if (seriesCount > 1) {
 279:             double seriesGap = space * getItemMargin() 
 280:                     / (categoryCount * (seriesCount - 1));
 281:             double seriesW = calculateSeriesWidth(space, domainAxis, 
 282:                     categoryCount, seriesCount);
 283:             barW0 = barW0 + row * (seriesW + seriesGap) 
 284:                           + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
 285:         }
 286:         else {
 287:             barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 288:                     dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() 
 289:                     / 2.0;
 290:         }
 291:         return barW0;
 292:     }
 293:     
 294:     /**
 295:      * Draws the bar for a single (series, category) data item.
 296:      *
 297:      * @param g2  the graphics device.
 298:      * @param state  the renderer state.
 299:      * @param dataArea  the data area.
 300:      * @param plot  the plot.
 301:      * @param domainAxis  the domain axis.
 302:      * @param rangeAxis  the range axis.
 303:      * @param dataset  the dataset.
 304:      * @param row  the row index (zero-based).
 305:      * @param column  the column index (zero-based).
 306:      * @param pass  the pass index.
 307:      */
 308:     public void drawItem(Graphics2D g2, CategoryItemRendererState state,
 309:             Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
 310:             ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
 311:             int pass) {
 312: 
 313:         // nothing is drawn for null values...
 314:         Number dataValue = dataset.getValue(row, column);
 315:         if (dataValue == null) {
 316:             return;
 317:         }
 318:         
 319:         double value = dataValue.doubleValue();
 320:         
 321:         PlotOrientation orientation = plot.getOrientation();
 322:         double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis, 
 323:                 state, row, column);
 324:         RectangleEdge edge = plot.getRangeAxisEdge();
 325:         double barL = rangeAxis.valueToJava2D(value, dataArea, edge);
 326: 
 327:         // draw the bar...
 328:         Line2D line = null;
 329:         double x = 0.0;
 330:         double y = 0.0;
 331:         if (orientation == PlotOrientation.HORIZONTAL) {
 332:             x = barL;
 333:             y = barW0 + state.getBarWidth() / 2.0;
 334:             line = new Line2D.Double(barL, barW0, barL, 
 335:                     barW0 + state.getBarWidth());
 336:         }
 337:         else {
 338:             x = barW0 + state.getBarWidth() / 2.0;
 339:             y = barL;
 340:             line = new Line2D.Double(barW0, barL, barW0 + state.getBarWidth(), 
 341:                     barL);
 342:         }
 343:         Stroke itemStroke = getItemStroke(row, column);
 344:         Paint itemPaint = getItemPaint(row, column);
 345:         g2.setStroke(itemStroke);
 346:         g2.setPaint(itemPaint);
 347:         g2.draw(line);
 348: 
 349:         CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 
 350:                 column);
 351:         if (generator != null && isItemLabelVisible(row, column)) {
 352:             drawItemLabel(g2, orientation, dataset, row, column, x, y, 
 353:                     (value < 0.0));
 354:         }        
 355:                 
 356:         // collect entity and tool tip information...
 357:         if (state.getInfo() != null) {
 358:             EntityCollection entities = state.getEntityCollection();
 359:             if (entities != null) {
 360:                 String tip = null;
 361:                 CategoryToolTipGenerator tipster = getToolTipGenerator(row, 
 362:                         column);
 363:                 if (tipster != null) {
 364:                     tip = tipster.generateToolTip(dataset, row, column);
 365:                 }
 366:                 String url = null;
 367:                 if (getItemURLGenerator(row, column) != null) {
 368:                     url = getItemURLGenerator(row, column).generateURL(dataset,
 369:                             row, column);
 370:                 }
 371:                 CategoryItemEntity entity = new CategoryItemEntity(
 372:                         line.getBounds(), tip, url, dataset, row, 
 373:                         dataset.getColumnKey(column), column);
 374:                 entities.add(entity);
 375:             }
 376: 
 377:         }
 378: 
 379:     }
 380: 
 381:     /**
 382:      * Calculates the available space for each series.
 383:      * 
 384:      * @param space  the space along the entire axis (in Java2D units).
 385:      * @param axis  the category axis.
 386:      * @param categories  the number of categories.
 387:      * @param series  the number of series.
 388:      * 
 389:      * @return The width of one series.
 390:      */
 391:     protected double calculateSeriesWidth(double space, CategoryAxis axis, 
 392:                                           int categories, int series) {
 393:         double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 
 394:                         - axis.getUpperMargin();
 395:         if (categories > 1) {
 396:             factor = factor - axis.getCategoryMargin();
 397:         }
 398:         return (space * factor) / (categories * series);
 399:     }
 400:     
 401:     /**
 402:      * Tests an object for equality with this instance.
 403:      * 
 404:      * @param obj  the object (<code>null</code> permitted).
 405:      * 
 406:      * @return A boolean.
 407:      */
 408:     public boolean equals(Object obj) {
 409:         if (obj == this) {
 410:             return true;
 411:         }
 412:         if (!(obj instanceof LevelRenderer)) {
 413:             return false;
 414:         }
 415:         if (!super.equals(obj)) {
 416:             return false;
 417:         }
 418:         LevelRenderer that = (LevelRenderer) obj;
 419:         if (this.itemMargin != that.itemMargin) {              
 420:             return false;
 421:         }
 422:         if (this.maxItemWidth != that.maxItemWidth) {
 423:             return false;
 424:         }
 425:         return true;
 426:     }
 427: 
 428: }