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