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