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: * LevelRenderer.java 29: * ------------------ 30: * (C) Copyright 2004-2008, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * Changes 36: * ------- 37: * 09-Jan-2004 : Version 1 (DG); 38: * 05-Nov-2004 : Modified drawItem() signature (DG); 39: * 20-Apr-2005 : Renamed CategoryLabelGenerator 40: * --> CategoryItemLabelGenerator (DG); 41: * ------------- JFREECHART 1.0.x --------------------------------------------- 42: * 23-Jan-2006 : Renamed getMaxItemWidth() --> getMaximumItemWidth() (DG); 43: * 13-May-2008 : Code clean-up (DG); 44: * 45: */ 46: 47: package org.jfree.chart.renderer.category; 48: 49: import java.awt.Graphics2D; 50: import java.awt.Paint; 51: import java.awt.Stroke; 52: import java.awt.geom.Line2D; 53: import java.awt.geom.Rectangle2D; 54: import java.io.Serializable; 55: 56: import org.jfree.chart.axis.CategoryAxis; 57: import org.jfree.chart.axis.ValueAxis; 58: import org.jfree.chart.entity.EntityCollection; 59: import org.jfree.chart.event.RendererChangeEvent; 60: import org.jfree.chart.labels.CategoryItemLabelGenerator; 61: import org.jfree.chart.plot.CategoryPlot; 62: import org.jfree.chart.plot.PlotOrientation; 63: import org.jfree.chart.plot.PlotRenderingInfo; 64: import org.jfree.data.category.CategoryDataset; 65: import org.jfree.ui.RectangleEdge; 66: import org.jfree.util.PublicCloneable; 67: 68: /** 69: * A {@link CategoryItemRenderer} that draws individual data items as 70: * horizontal lines, spaced in the same way as bars in a bar chart. 71: */ 72: public class LevelRenderer extends AbstractCategoryItemRenderer 73: implements Cloneable, PublicCloneable, Serializable { 74: 75: /** For serialization. */ 76: private static final long serialVersionUID = -8204856624355025117L; 77: 78: /** The default item margin percentage. */ 79: public static final double DEFAULT_ITEM_MARGIN = 0.20; 80: 81: /** The margin between items within a category. */ 82: private double itemMargin; 83: 84: /** The maximum item width as a percentage of the available space. */ 85: private double maxItemWidth; 86: 87: /** 88: * Creates a new renderer with default settings. 89: */ 90: public LevelRenderer() { 91: super(); 92: this.itemMargin = DEFAULT_ITEM_MARGIN; 93: this.maxItemWidth = 1.0; // 100 percent, so it will not apply unless 94: // changed 95: } 96: 97: /** 98: * Returns the item margin. 99: * 100: * @return The margin. 101: * 102: * @see #setItemMargin(double) 103: */ 104: public double getItemMargin() { 105: return this.itemMargin; 106: } 107: 108: /** 109: * Sets the item margin and sends a {@link RendererChangeEvent} to all 110: * registered listeners. The value is expressed as a percentage of the 111: * available width for plotting all the bars, with the resulting amount to 112: * be distributed between all the bars evenly. 113: * 114: * @param percent the new margin. 115: * 116: * @see #getItemMargin() 117: */ 118: public void setItemMargin(double percent) { 119: this.itemMargin = percent; 120: fireChangeEvent(); 121: } 122: 123: /** 124: * Returns the maximum width, as a percentage of the available drawing 125: * space. 126: * 127: * @return The maximum width. 128: * 129: * @see #setMaximumItemWidth(double) 130: */ 131: public double getMaximumItemWidth() { 132: return getMaxItemWidth(); 133: } 134: 135: /** 136: * Sets the maximum item width, which is specified as a percentage of the 137: * available space for all items, and sends a {@link RendererChangeEvent} 138: * to all registered listeners. 139: * 140: * @param percent the percent. 141: * 142: * @see #getMaximumItemWidth() 143: */ 144: public void setMaximumItemWidth(double percent) { 145: setMaxItemWidth(percent); 146: } 147: 148: /** 149: * Initialises the renderer and returns a state object that will be passed 150: * to subsequent calls to the drawItem method. 151: * <p> 152: * This method gets called once at the start of the process of drawing a 153: * chart. 154: * 155: * @param g2 the graphics device. 156: * @param dataArea the area in which the data is to be plotted. 157: * @param plot the plot. 158: * @param rendererIndex the renderer index. 159: * @param info collects chart rendering information for return to caller. 160: * 161: * @return The renderer state. 162: */ 163: public CategoryItemRendererState initialise(Graphics2D g2, 164: Rectangle2D dataArea, CategoryPlot plot, int rendererIndex, 165: PlotRenderingInfo info) { 166: 167: CategoryItemRendererState state = super.initialise(g2, dataArea, plot, 168: rendererIndex, info); 169: calculateItemWidth(plot, dataArea, rendererIndex, state); 170: return state; 171: 172: } 173: 174: /** 175: * Calculates the bar width and stores it in the renderer state. 176: * 177: * @param plot the plot. 178: * @param dataArea the data area. 179: * @param rendererIndex the renderer index. 180: * @param state the renderer state. 181: */ 182: protected void calculateItemWidth(CategoryPlot plot, 183: Rectangle2D dataArea, int rendererIndex, 184: CategoryItemRendererState state) { 185: 186: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 187: CategoryDataset dataset = plot.getDataset(rendererIndex); 188: if (dataset != null) { 189: int columns = dataset.getColumnCount(); 190: int rows = dataset.getRowCount(); 191: double space = 0.0; 192: PlotOrientation orientation = plot.getOrientation(); 193: if (orientation == PlotOrientation.HORIZONTAL) { 194: space = dataArea.getHeight(); 195: } 196: else if (orientation == PlotOrientation.VERTICAL) { 197: space = dataArea.getWidth(); 198: } 199: double maxWidth = space * getMaximumItemWidth(); 200: double categoryMargin = 0.0; 201: double currentItemMargin = 0.0; 202: if (columns > 1) { 203: categoryMargin = domainAxis.getCategoryMargin(); 204: } 205: if (rows > 1) { 206: currentItemMargin = getItemMargin(); 207: } 208: double used = space * (1 - domainAxis.getLowerMargin() 209: - domainAxis.getUpperMargin() 210: - categoryMargin - currentItemMargin); 211: if ((rows * columns) > 0) { 212: state.setBarWidth(Math.min(used / (rows * columns), maxWidth)); 213: } 214: else { 215: state.setBarWidth(Math.min(used, maxWidth)); 216: } 217: } 218: } 219: 220: /** 221: * Calculates the coordinate of the first "side" of a bar. This will be 222: * the minimum x-coordinate for a vertical bar, and the minimum 223: * y-coordinate for a horizontal bar. 224: * 225: * @param plot the plot. 226: * @param orientation the plot orientation. 227: * @param dataArea the data area. 228: * @param domainAxis the domain axis. 229: * @param state the renderer state (has the bar width precalculated). 230: * @param row the row index. 231: * @param column the column index. 232: * 233: * @return The coordinate. 234: */ 235: protected double calculateBarW0(CategoryPlot plot, 236: PlotOrientation orientation, 237: Rectangle2D dataArea, 238: CategoryAxis domainAxis, 239: CategoryItemRendererState state, 240: int row, 241: int column) { 242: // calculate bar width... 243: double space = 0.0; 244: if (orientation == PlotOrientation.HORIZONTAL) { 245: space = dataArea.getHeight(); 246: } 247: else { 248: space = dataArea.getWidth(); 249: } 250: double barW0 = domainAxis.getCategoryStart(column, getColumnCount(), 251: dataArea, plot.getDomainAxisEdge()); 252: int seriesCount = getRowCount(); 253: int categoryCount = getColumnCount(); 254: if (seriesCount > 1) { 255: double seriesGap = space * getItemMargin() 256: / (categoryCount * (seriesCount - 1)); 257: double seriesW = calculateSeriesWidth(space, domainAxis, 258: categoryCount, seriesCount); 259: barW0 = barW0 + row * (seriesW + seriesGap) 260: + (seriesW / 2.0) - (state.getBarWidth() / 2.0); 261: } 262: else { 263: barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(), 264: dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() 265: / 2.0; 266: } 267: return barW0; 268: } 269: 270: /** 271: * Draws the bar for a single (series, category) data item. 272: * 273: * @param g2 the graphics device. 274: * @param state the renderer state. 275: * @param dataArea the data area. 276: * @param plot the plot. 277: * @param domainAxis the domain axis. 278: * @param rangeAxis the range axis. 279: * @param dataset the dataset. 280: * @param row the row index (zero-based). 281: * @param column the column index (zero-based). 282: * @param pass the pass index. 283: */ 284: public void drawItem(Graphics2D g2, CategoryItemRendererState state, 285: Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 286: ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, 287: int pass) { 288: 289: // nothing is drawn for null values... 290: Number dataValue = dataset.getValue(row, column); 291: if (dataValue == null) { 292: return; 293: } 294: 295: double value = dataValue.doubleValue(); 296: 297: PlotOrientation orientation = plot.getOrientation(); 298: double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis, 299: state, row, column); 300: RectangleEdge edge = plot.getRangeAxisEdge(); 301: double barL = rangeAxis.valueToJava2D(value, dataArea, edge); 302: 303: // draw the bar... 304: Line2D line = null; 305: double x = 0.0; 306: double y = 0.0; 307: if (orientation == PlotOrientation.HORIZONTAL) { 308: x = barL; 309: y = barW0 + state.getBarWidth() / 2.0; 310: line = new Line2D.Double(barL, barW0, barL, 311: barW0 + state.getBarWidth()); 312: } 313: else { 314: x = barW0 + state.getBarWidth() / 2.0; 315: y = barL; 316: line = new Line2D.Double(barW0, barL, barW0 + state.getBarWidth(), 317: barL); 318: } 319: Stroke itemStroke = getItemStroke(row, column); 320: Paint itemPaint = getItemPaint(row, column); 321: g2.setStroke(itemStroke); 322: g2.setPaint(itemPaint); 323: g2.draw(line); 324: 325: CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 326: column); 327: if (generator != null && isItemLabelVisible(row, column)) { 328: drawItemLabel(g2, orientation, dataset, row, column, x, y, 329: (value < 0.0)); 330: } 331: 332: // add an item entity, if this information is being collected 333: EntityCollection entities = state.getEntityCollection(); 334: if (entities != null) { 335: addItemEntity(entities, dataset, row, column, line.getBounds()); 336: } 337: 338: } 339: 340: /** 341: * Calculates the available space for each series. 342: * 343: * @param space the space along the entire axis (in Java2D units). 344: * @param axis the category axis. 345: * @param categories the number of categories. 346: * @param series the number of series. 347: * 348: * @return The width of one series. 349: */ 350: protected double calculateSeriesWidth(double space, CategoryAxis axis, 351: int categories, int series) { 352: double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 353: - axis.getUpperMargin(); 354: if (categories > 1) { 355: factor = factor - axis.getCategoryMargin(); 356: } 357: return (space * factor) / (categories * series); 358: } 359: 360: /** 361: * Tests an object for equality with this instance. 362: * 363: * @param obj the object (<code>null</code> permitted). 364: * 365: * @return A boolean. 366: */ 367: public boolean equals(Object obj) { 368: if (obj == this) { 369: return true; 370: } 371: if (!(obj instanceof LevelRenderer)) { 372: return false; 373: } 374: LevelRenderer that = (LevelRenderer) obj; 375: if (this.itemMargin != that.itemMargin) { 376: return false; 377: } 378: if (this.maxItemWidth != that.maxItemWidth) { 379: return false; 380: } 381: return super.equals(obj); 382: } 383: 384: /** 385: * Returns the maximum width, as a percentage of the available drawing 386: * space. 387: * 388: * @return The maximum width. 389: * 390: * @deprecated Use {@link #getMaximumItemWidth()} instead. 391: */ 392: public double getMaxItemWidth() { 393: return this.maxItemWidth; 394: } 395: 396: /** 397: * Sets the maximum item width, which is specified as a percentage of the 398: * available space for all items, and sends a {@link RendererChangeEvent} 399: * to all registered listeners. 400: * 401: * @param percent the percent. 402: * 403: * @deprecated Use {@link #setMaximumItemWidth(double)} instead. 404: */ 405: public void setMaxItemWidth(double percent) { 406: this.maxItemWidth = percent; 407: fireChangeEvent(); 408: } 409: 410: }