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: * 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: }