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:  * Changes
  37:  * -------
  38:  * 21-Apr-2004 : Version 1, contributed by Brian Cole (DG);
  39:  * 22-Apr-2004 : Fixed Checkstyle complaints (DG);
  40:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  41:  * 08-Mar-2005 : Added equals() method (DG);
  42:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  43:  * 30-Nov-2006 : Added checks for series visibility (DG);
  44:  * 22-Feb-2007 : Use new state object for reusable line, enable chart entities 
  45:  *               (for tooltips, URLs), added new getLegendItem() override (DG);
  46:  * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
  47:  * 18-May-2007 : Set dataset and seriesKey for LegendItem (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 = p.getDataset(datasetIndex);
 175:         String label = getLegendItemLabelGenerator().generateLabel(dataset, 
 176:                 series);
 177:         String description = label;
 178:         String toolTipText = null; 
 179:         if (getLegendItemToolTipGenerator() != null) {
 180:             toolTipText = getLegendItemToolTipGenerator().generateLabel(
 181:                     dataset, series);   
 182:         }
 183:         String urlText = null;
 184:         if (getLegendItemURLGenerator() != null) {
 185:             urlText = getLegendItemURLGenerator().generateLabel(dataset, 
 186:                     series);   
 187:         }
 188:         Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0);
 189:         Paint paint = lookupSeriesPaint(series);
 190:      
 191:         LegendItem item = new LegendItem(label, description, toolTipText, 
 192:                 urlText, shape, paint);
 193:         item.setSeriesKey(dataset.getRowKey(series));
 194:         item.setSeriesIndex(series);
 195:         item.setDataset(dataset);
 196:         item.setDatasetIndex(datasetIndex);
 197:         return item;
 198:     }
 199: 
 200:     /**
 201:      * Creates a new state instance.  This method is called from 
 202:      * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int, 
 203:      * PlotRenderingInfo)}, and we override it to ensure that the state
 204:      * contains a working Line2D instance.
 205:      * 
 206:      * @param info  the plot rendering info (<code>null</code> is permitted).
 207:      * 
 208:      * @return A new state instance.
 209:      */
 210:     protected CategoryItemRendererState createState(PlotRenderingInfo info) {
 211:         return new State(info);
 212:     }
 213: 
 214:     /**
 215:      * Draws a line taking into account the specified orientation.
 216:      * <p>
 217:      * In version 1.0.5, the signature of this method was changed by the 
 218:      * addition of the 'state' parameter.  This is an incompatible change, but
 219:      * is considered a low risk because it is unlikely that anyone has 
 220:      * subclassed this renderer.  If this *does* cause trouble for you, please
 221:      * report it as a bug.
 222:      * 
 223:      * @param g2  the graphics device.
 224:      * @param state  the renderer state.
 225:      * @param orientation  the plot orientation.
 226:      * @param x0  the x-coordinate for the start of the line.
 227:      * @param y0  the y-coordinate for the start of the line.
 228:      * @param x1  the x-coordinate for the end of the line.
 229:      * @param y1  the y-coordinate for the end of the line.
 230:      */
 231:     protected void drawLine(Graphics2D g2, State state, 
 232:             PlotOrientation orientation, double x0, double y0, double x1, 
 233:             double y1) {
 234:      
 235:         if (orientation == PlotOrientation.VERTICAL) {
 236:             state.line.setLine(x0, y0, x1, y1);
 237:             g2.draw(state.line);
 238:         }
 239:         else if (orientation == PlotOrientation.HORIZONTAL) {
 240:             state.line.setLine(y0, x0, y1, x1); // switch x and y
 241:             g2.draw(state.line);
 242:         }
 243: 
 244:     }
 245: 
 246:     /**
 247:      * Draw a single data item.
 248:      *
 249:      * @param g2  the graphics device.
 250:      * @param state  the renderer state.
 251:      * @param dataArea  the area in which the data is drawn.
 252:      * @param plot  the plot.
 253:      * @param domainAxis  the domain axis.
 254:      * @param rangeAxis  the range axis.
 255:      * @param dataset  the dataset.
 256:      * @param row  the row index (zero-based).
 257:      * @param column  the column index (zero-based).
 258:      * @param pass  the pass index.
 259:      */
 260:     public void drawItem(Graphics2D g2,
 261:                          CategoryItemRendererState state,
 262:                          Rectangle2D dataArea,
 263:                          CategoryPlot plot,
 264:                          CategoryAxis domainAxis,
 265:                          ValueAxis rangeAxis,
 266:                          CategoryDataset dataset,
 267:                          int row,
 268:                          int column,
 269:                          int pass) {
 270: 
 271:         // do nothing if item is not visible
 272:         if (!getItemVisible(row, column)) {
 273:             return;   
 274:         }
 275:         
 276:         Number value = dataset.getValue(row, column);
 277:         if (value == null) {
 278:             return;
 279:         }
 280:         PlotOrientation orientation = plot.getOrientation();
 281: 
 282:         // current data point...
 283:         double x1s = domainAxis.getCategoryStart(column, getColumnCount(), 
 284:                 dataArea, plot.getDomainAxisEdge());
 285:         double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 286:                 dataArea, plot.getDomainAxisEdge());
 287:         double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
 288:         double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea, 
 289:                 plot.getRangeAxisEdge());
 290:         g2.setPaint(getItemPaint(row, column));
 291:         g2.setStroke(getItemStroke(row, column));
 292: 
 293:         if (column != 0) {
 294:             Number previousValue = dataset.getValue(row, column - 1);
 295:             if (previousValue != null) {
 296:                 // previous data point...
 297:                 double previous = previousValue.doubleValue();
 298:                 double x0s = domainAxis.getCategoryStart(column - 1, 
 299:                         getColumnCount(), dataArea, plot.getDomainAxisEdge());
 300:                 double x0 = domainAxis.getCategoryMiddle(column - 1, 
 301:                         getColumnCount(), dataArea, plot.getDomainAxisEdge());
 302:                 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
 303:                 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 
 304:                         plot.getRangeAxisEdge());
 305:                 if (getStagger()) {
 306:                     int xStagger = row * STAGGER_WIDTH;
 307:                     if (xStagger > (x1s - x0e)) {
 308:                         xStagger = (int) (x1s - x0e);
 309:                     }
 310:                     x1s = x0e + xStagger;
 311:                 }
 312:                 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0); 
 313:                 // extend x0's flat bar
 314: 
 315:                 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1); 
 316:                 // upright bar
 317:            }
 318:        }
 319:        drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1); 
 320:        // x1's flat bar
 321: 
 322:        // draw the item labels if there are any...
 323:        if (isItemLabelVisible(row, column)) {
 324:             drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 
 325:                     (value.doubleValue() < 0.0));
 326:        }
 327: 
 328:        // add an item entity, if this information is being collected
 329:        EntityCollection entities = state.getEntityCollection();
 330:        if (entities != null) {
 331:            Rectangle2D hotspot = new Rectangle2D.Double();
 332:            if (orientation == PlotOrientation.VERTICAL) {
 333:                hotspot.setRect(x1s, y1, x1e - x1s, 4.0);
 334:            }
 335:            else {
 336:                hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s);
 337:            }
 338:            addItemEntity(entities, dataset, row, column, hotspot);
 339:        }
 340: 
 341:     }
 342:     
 343:     /**
 344:      * Tests this renderer for equality with an arbitrary object.
 345:      * 
 346:      * @param obj  the object (<code>null</code> permitted).
 347:      * 
 348:      * @return A boolean.
 349:      */
 350:     public boolean equals(Object obj) {
 351:         if (obj == this) {
 352:             return true;   
 353:         }
 354:         if (!(obj instanceof CategoryStepRenderer)) {
 355:             return false;   
 356:         }
 357:         CategoryStepRenderer that = (CategoryStepRenderer) obj;
 358:         if (this.stagger != that.stagger) {
 359:             return false;   
 360:         }
 361:         return super.equals(obj);
 362:     }
 363: 
 364: }