Frames | No Frames |
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: * CategoryStepRenderer.java 29: * ------------------------- 30: * 31: * (C) Copyright 2004-2008, 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, Serializable { 78: 79: /** 80: * State information for the renderer. 81: */ 82: protected static class State extends CategoryItemRendererState { 83: 84: /** 85: * A working line for re-use to avoid creating large numbers of 86: * objects. 87: */ 88: public Line2D line; 89: 90: /** 91: * Creates a new state instance. 92: * 93: * @param info collects plot rendering information (<code>null</code> 94: * permitted). 95: */ 96: public State(PlotRenderingInfo info) { 97: super(info); 98: this.line = new Line2D.Double(); 99: } 100: 101: } 102: 103: /** For serialization. */ 104: private static final long serialVersionUID = -5121079703118261470L; 105: 106: /** The stagger width. */ 107: public static final int STAGGER_WIDTH = 5; // could make this configurable 108: 109: /** 110: * A flag that controls whether or not the steps for multiple series are 111: * staggered. 112: */ 113: private boolean stagger = false; 114: 115: /** 116: * Creates a new renderer (stagger defaults to <code>false</code>). 117: */ 118: public CategoryStepRenderer() { 119: this(false); 120: } 121: 122: /** 123: * Creates a new renderer. 124: * 125: * @param stagger should the horizontal part of the step be staggered by 126: * series? 127: */ 128: public CategoryStepRenderer(boolean stagger) { 129: this.stagger = stagger; 130: } 131: 132: /** 133: * Returns the flag that controls whether the series steps are staggered. 134: * 135: * @return A boolean. 136: */ 137: public boolean getStagger() { 138: return this.stagger; 139: } 140: 141: /** 142: * Sets the flag that controls whether or not the series steps are 143: * staggered and sends a {@link RendererChangeEvent} to all registered 144: * listeners. 145: * 146: * @param shouldStagger a boolean. 147: */ 148: public void setStagger(boolean shouldStagger) { 149: this.stagger = shouldStagger; 150: fireChangeEvent(); 151: } 152: 153: /** 154: * Returns a legend item for a series. 155: * 156: * @param datasetIndex the dataset index (zero-based). 157: * @param series the series index (zero-based). 158: * 159: * @return The legend item. 160: */ 161: public LegendItem getLegendItem(int datasetIndex, int series) { 162: 163: CategoryPlot p = getPlot(); 164: if (p == null) { 165: return null; 166: } 167: 168: // check that a legend item needs to be displayed... 169: if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 170: return null; 171: } 172: 173: CategoryDataset dataset = p.getDataset(datasetIndex); 174: String label = getLegendItemLabelGenerator().generateLabel(dataset, 175: series); 176: String description = label; 177: String toolTipText = null; 178: if (getLegendItemToolTipGenerator() != null) { 179: toolTipText = getLegendItemToolTipGenerator().generateLabel( 180: dataset, series); 181: } 182: String urlText = null; 183: if (getLegendItemURLGenerator() != null) { 184: urlText = getLegendItemURLGenerator().generateLabel(dataset, 185: series); 186: } 187: Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0); 188: Paint paint = lookupSeriesPaint(series); 189: 190: LegendItem item = new LegendItem(label, description, toolTipText, 191: urlText, shape, paint); 192: item.setSeriesKey(dataset.getRowKey(series)); 193: item.setSeriesIndex(series); 194: item.setDataset(dataset); 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: }