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

   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:  * CategoryStepRenderer.java
  29:  * -------------------------
  30:  *
  31:  * (C) Copyright 2004-2007, by Brian Cole and Contributors.
  32:  *
  33:  * Original Author:  Brian Cole;
  34:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  35:  *
  36:  * $Id: CategoryStepRenderer.java,v 1.5.2.3 2007/02/22 11:38:18 mungady Exp $
  37:  *
  38:  * Changes
  39:  * -------
  40:  * 21-Apr-2004 : Version 1, contributed by Brian Cole (DG);
  41:  * 22-Apr-2004 : Fixed Checkstyle complaints (DG);
  42:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  43:  * 08-Mar-2005 : Added equals() method (DG);
  44:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  45:  * 30-Nov-2006 : Added checks for series visibility (DG);
  46:  * 22-Feb-2007 : Use new state object for reusable line, enable chart entities 
  47:  *               (for tooltips, URLs), added new getLegendItem() override (DG);
  48:  * 
  49:  */
  50: 
  51: package org.jfree.chart.renderer.category;
  52: 
  53: import java.awt.Graphics2D;
  54: import java.awt.Paint;
  55: import java.awt.Shape;
  56: import java.awt.geom.Line2D;
  57: import java.awt.geom.Rectangle2D;
  58: import java.io.Serializable;
  59: 
  60: import org.jfree.chart.LegendItem;
  61: import org.jfree.chart.axis.CategoryAxis;
  62: import org.jfree.chart.axis.ValueAxis;
  63: import org.jfree.chart.entity.EntityCollection;
  64: import org.jfree.chart.event.RendererChangeEvent;
  65: import org.jfree.chart.plot.CategoryPlot;
  66: import org.jfree.chart.plot.PlotOrientation;
  67: import org.jfree.chart.plot.PlotRenderingInfo;
  68: import org.jfree.chart.renderer.xy.XYStepRenderer;
  69: import org.jfree.data.category.CategoryDataset;
  70: import org.jfree.util.PublicCloneable;
  71: 
  72: /**
  73:  * A "step" renderer similar to {@link XYStepRenderer} but
  74:  * that can be used with the {@link CategoryPlot} class.
  75:  */
  76: public class CategoryStepRenderer extends AbstractCategoryItemRenderer
  77:                                   implements Cloneable, PublicCloneable, 
  78:                                              Serializable {
  79: 
  80:     /**
  81:      * State information for the renderer.
  82:      */
  83:     protected static class State extends CategoryItemRendererState {
  84: 
  85:         /** 
  86:          * A working line for re-use to avoid creating large numbers of
  87:          * objects.
  88:          */
  89:         public Line2D line;
  90:         
  91:         /**
  92:          * Creates a new state instance.
  93:          * 
  94:          * @param info  collects plot rendering information (<code>null</code> 
  95:          *              permitted).
  96:          */
  97:         public State(PlotRenderingInfo info) {
  98:             super(info);
  99:             this.line = new Line2D.Double();
 100:         }
 101:         
 102:     }
 103:     
 104:     /** For serialization. */
 105:     private static final long serialVersionUID = -5121079703118261470L;
 106:     
 107:     /** The stagger width. */
 108:     public static final int STAGGER_WIDTH = 5; // could make this configurable
 109:   
 110:     /** 
 111:      * A flag that controls whether or not the steps for multiple series are 
 112:      * staggered. 
 113:      */
 114:     private boolean stagger = false;
 115: 
 116:     /** 
 117:      * Creates a new renderer (stagger defaults to <code>false</code>).
 118:      */
 119:     public CategoryStepRenderer() {
 120:         this(false);
 121:     }
 122:     
 123:     /**
 124:      * Creates a new renderer.
 125:      *  
 126:      * @param stagger  should the horizontal part of the step be staggered by 
 127:      *                 series? 
 128:      */
 129:     public CategoryStepRenderer(boolean stagger) {
 130:         this.stagger = stagger;
 131:     }
 132:   
 133:     /**
 134:      * Returns the flag that controls whether the series steps are staggered.
 135:      * 
 136:      * @return A boolean.
 137:      */
 138:     public boolean getStagger() {
 139:         return this.stagger;
 140:     }
 141:     
 142:     /**
 143:      * Sets the flag that controls whether or not the series steps are 
 144:      * staggered and sends a {@link RendererChangeEvent} to all registered
 145:      * listeners.
 146:      * 
 147:      * @param shouldStagger  a boolean.
 148:      */
 149:     public void setStagger(boolean shouldStagger) {
 150:         this.stagger = shouldStagger;
 151:         notifyListeners(new RendererChangeEvent(this));
 152:     }
 153: 
 154:     /**
 155:      * Returns a legend item for a series.
 156:      *
 157:      * @param datasetIndex  the dataset index (zero-based).
 158:      * @param series  the series index (zero-based).
 159:      *
 160:      * @return The legend item.
 161:      */
 162:     public LegendItem getLegendItem(int datasetIndex, int series) {
 163: 
 164:         CategoryPlot p = getPlot();
 165:         if (p == null) {
 166:             return null;
 167:         }
 168: 
 169:         // check that a legend item needs to be displayed...
 170:         if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
 171:             return null;
 172:         }
 173: 
 174:         CategoryDataset dataset;
 175:         dataset = p.getDataset(datasetIndex);
 176:         String label = getLegendItemLabelGenerator().generateLabel(dataset, 
 177:                 series);
 178:         String description = label;
 179:         String toolTipText = null; 
 180:         if (getLegendItemToolTipGenerator() != null) {
 181:             toolTipText = getLegendItemToolTipGenerator().generateLabel(
 182:                     dataset, series);   
 183:         }
 184:         String urlText = null;
 185:         if (getLegendItemURLGenerator() != null) {
 186:             urlText = getLegendItemURLGenerator().generateLabel(dataset, 
 187:                     series);   
 188:         }
 189:         Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0);
 190:         Paint paint = getSeriesPaint(series);
 191:      
 192:         LegendItem item = new LegendItem(label, description, toolTipText, 
 193:                 urlText, shape, paint);
 194:         item.setSeriesIndex(series);
 195:         item.setDatasetIndex(datasetIndex);
 196:         return item;
 197:     }
 198: 
 199:     /**
 200:      * Creates a new state instance.  This method is called from 
 201:      * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int, 
 202:      * PlotRenderingInfo)}, and we override it to ensure that the state
 203:      * contains a working Line2D instance.
 204:      * 
 205:      * @param info  the plot rendering info (<code>null</code> is permitted).
 206:      * 
 207:      * @return A new state instance.
 208:      */
 209:     protected CategoryItemRendererState createState(PlotRenderingInfo info) {
 210:         return new State(info);
 211:     }
 212: 
 213:     /**
 214:      * Draws a line taking into account the specified orientation.
 215:      * <p>
 216:      * In version 1.0.5, the signature of this method was changed by the 
 217:      * addition of the 'state' parameter.  This is an incompatible change, but
 218:      * is considered a low risk because it is unlikely that anyone has 
 219:      * subclassed this renderer.  If this *does* cause trouble for you, please
 220:      * report it as a bug.
 221:      * 
 222:      * @param g2  the graphics device.
 223:      * @param state  the renderer state.
 224:      * @param orientation  the plot orientation.
 225:      * @param x0  the x-coordinate for the start of the line.
 226:      * @param y0  the y-coordinate for the start of the line.
 227:      * @param x1  the x-coordinate for the end of the line.
 228:      * @param y1  the y-coordinate for the end of the line.
 229:      */
 230:     protected void drawLine(Graphics2D g2, State state, 
 231:             PlotOrientation orientation, double x0, double y0, double x1, 
 232:             double y1) {
 233:      
 234:         if (orientation == PlotOrientation.VERTICAL) {
 235:             state.line.setLine(x0, y0, x1, y1);
 236:             g2.draw(state.line);
 237:         }
 238:         else if (orientation == PlotOrientation.HORIZONTAL) {
 239:             state.line.setLine(y0, x0, y1, x1); // switch x and y
 240:             g2.draw(state.line);
 241:         }
 242: 
 243:     }
 244: 
 245:     /**
 246:      * Draw a single data item.
 247:      *
 248:      * @param g2  the graphics device.
 249:      * @param state  the renderer state.
 250:      * @param dataArea  the area in which the data is drawn.
 251:      * @param plot  the plot.
 252:      * @param domainAxis  the domain axis.
 253:      * @param rangeAxis  the range axis.
 254:      * @param dataset  the dataset.
 255:      * @param row  the row index (zero-based).
 256:      * @param column  the column index (zero-based).
 257:      * @param pass  the pass index.
 258:      */
 259:     public void drawItem(Graphics2D g2,
 260:                          CategoryItemRendererState state,
 261:                          Rectangle2D dataArea,
 262:                          CategoryPlot plot,
 263:                          CategoryAxis domainAxis,
 264:                          ValueAxis rangeAxis,
 265:                          CategoryDataset dataset,
 266:                          int row,
 267:                          int column,
 268:                          int pass) {
 269: 
 270:         // do nothing if item is not visible
 271:         if (!getItemVisible(row, column)) {
 272:             return;   
 273:         }
 274:         
 275:         Number value = dataset.getValue(row, column);
 276:         if (value == null) {
 277:             return;
 278:         }
 279:         PlotOrientation orientation = plot.getOrientation();
 280: 
 281:         // current data point...
 282:         double x1s = domainAxis.getCategoryStart(column, getColumnCount(), 
 283:                 dataArea, plot.getDomainAxisEdge());
 284:         double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 285:                 dataArea, plot.getDomainAxisEdge());
 286:         double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
 287:         double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 
 288:                 plot.getRangeAxisEdge());
 289:         g2.setPaint(getItemPaint(row, column));
 290:         g2.setStroke(getItemStroke(row, column));
 291: 
 292:         if (column != 0) {
 293:             Number previousValue = dataset.getValue(row, column - 1);
 294:             if (previousValue != null) {
 295:                 // previous data point...
 296:                 double previous = previousValue.doubleValue();
 297:                 double x0s = domainAxis.getCategoryStart(column - 1, 
 298:                         getColumnCount(), dataArea, plot.getDomainAxisEdge());
 299:                 double x0 = domainAxis.getCategoryMiddle(column - 1, 
 300:                         getColumnCount(), dataArea, plot.getDomainAxisEdge());
 301:                 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
 302:                 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 
 303:                         plot.getRangeAxisEdge());
 304:                 if (getStagger()) {
 305:                     int xStagger = row * STAGGER_WIDTH;
 306:                     if (xStagger > (x1s - x0e)) {
 307:                         xStagger = (int) (x1s - x0e);
 308:                     }
 309:                     x1s = x0e + xStagger;
 310:                 }
 311:                 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0); 
 312:                 // extend x0's flat bar
 313: 
 314:                 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1); 
 315:                 // upright bar
 316:            }
 317:        }
 318:        drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1); 
 319:        // x1's flat bar
 320: 
 321:        // draw the item labels if there are any...
 322:        if (isItemLabelVisible(row, column)) {
 323:             drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 
 324:                     (value.doubleValue() < 0.0));
 325:        }
 326: 
 327:        // add an item entity, if this information is being collected
 328:        EntityCollection entities = state.getEntityCollection();
 329:        if (entities != null) {
 330:            Rectangle2D hotspot = new Rectangle2D.Double();
 331:            if (orientation == PlotOrientation.VERTICAL) {
 332:                hotspot.setRect(x1s, y1, x1e - x1s, 4.0);
 333:            }
 334:            else {
 335:                hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s);
 336:            }
 337:            addItemEntity(entities, dataset, row, column, hotspot);
 338:        }
 339: 
 340:     }
 341:     
 342:     /**
 343:      * Tests this renderer for equality with an arbitrary object.
 344:      * 
 345:      * @param obj  the object (<code>null</code> permitted).
 346:      * 
 347:      * @return A boolean.
 348:      */
 349:     public boolean equals(Object obj) {
 350:         if (obj == this) {
 351:             return true;   
 352:         }
 353:         if (!(obj instanceof CategoryStepRenderer)) {
 354:             return false;   
 355:         }
 356:         CategoryStepRenderer that = (CategoryStepRenderer) obj;
 357:         if (this.stagger != that.stagger) {
 358:             return false;   
 359:         }
 360:         return super.equals(obj);
 361:     }
 362: 
 363: }