001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------ 028 * LevelRenderer.java 029 * ------------------ 030 * (C) Copyright 2004, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: LevelRenderer.java,v 1.7.2.2 2005/12/02 09:56:00 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 09-Jan-2004 : Version 1 (DG); 040 * 05-Nov-2004 : Modified drawItem() signature (DG); 041 * 20-Apr-2005 : Renamed CategoryLabelGenerator 042 * --> CategoryItemLabelGenerator (DG); 043 * 044 */ 045 046 package org.jfree.chart.renderer.category; 047 048 import java.awt.Graphics2D; 049 import java.awt.Paint; 050 import java.awt.Stroke; 051 import java.awt.geom.Line2D; 052 import java.awt.geom.Rectangle2D; 053 import java.io.Serializable; 054 055 import org.jfree.chart.axis.CategoryAxis; 056 import org.jfree.chart.axis.ValueAxis; 057 import org.jfree.chart.entity.CategoryItemEntity; 058 import org.jfree.chart.entity.EntityCollection; 059 import org.jfree.chart.event.RendererChangeEvent; 060 import org.jfree.chart.labels.CategoryItemLabelGenerator; 061 import org.jfree.chart.labels.CategoryToolTipGenerator; 062 import org.jfree.chart.plot.CategoryPlot; 063 import org.jfree.chart.plot.PlotOrientation; 064 import org.jfree.chart.plot.PlotRenderingInfo; 065 import org.jfree.data.category.CategoryDataset; 066 import org.jfree.ui.RectangleEdge; 067 import org.jfree.util.PublicCloneable; 068 069 /** 070 * A {@link CategoryItemRenderer} that draws individual data items as 071 * horizontal lines, spaced in the same way as bars in a bar chart. 072 */ 073 public class LevelRenderer extends AbstractCategoryItemRenderer 074 implements Cloneable, PublicCloneable, Serializable { 075 076 /** For serialization. */ 077 private static final long serialVersionUID = -8204856624355025117L; 078 079 /** The default item margin percentage. */ 080 public static final double DEFAULT_ITEM_MARGIN = 0.20; 081 082 /** The margin between items within a category. */ 083 private double itemMargin; 084 085 /** The maximum item width as a percentage of the available space. */ 086 private double maxItemWidth; 087 088 /** 089 * Creates a new renderer with default settings. 090 */ 091 public LevelRenderer() { 092 super(); 093 this.itemMargin = DEFAULT_ITEM_MARGIN; 094 this.maxItemWidth = 1.0; // 100 percent, so it will not apply unless 095 // changed 096 } 097 098 /** 099 * Returns the item margin. 100 * 101 * @return The margin. 102 */ 103 public double getItemMargin() { 104 return this.itemMargin; 105 } 106 107 /** 108 * Sets the item margin. The value is expressed as a percentage of the 109 * available width for plotting all the bars, with the resulting amount to 110 * be distributed between all the bars evenly. 111 * 112 * @param percent the new margin. 113 */ 114 public void setItemMargin(double percent) { 115 this.itemMargin = percent; 116 notifyListeners(new RendererChangeEvent(this)); 117 } 118 119 /** 120 * Returns the maximum width, as a percentage of the available drawing 121 * space. 122 * 123 * @return The maximum width. 124 */ 125 public double getMaxItemWidth() { 126 return this.maxItemWidth; 127 } 128 129 /** 130 * Sets the maximum item width, which is specified as a percentage of the 131 * available space for all items, and sends a {@link RendererChangeEvent} 132 * to all registered listeners. 133 * 134 * @param percent the percent. 135 */ 136 public void setMaxItemWidth(double percent) { 137 this.maxItemWidth = percent; 138 notifyListeners(new RendererChangeEvent(this)); 139 } 140 141 /** 142 * Initialises the renderer and returns a state object that will be passed 143 * to subsequent calls to the drawItem method. 144 * <p> 145 * This method gets called once at the start of the process of drawing a 146 * chart. 147 * 148 * @param g2 the graphics device. 149 * @param dataArea the area in which the data is to be plotted. 150 * @param plot the plot. 151 * @param rendererIndex the renderer index. 152 * @param info collects chart rendering information for return to caller. 153 * 154 * @return The renderer state. 155 * 156 */ 157 public CategoryItemRendererState initialise(Graphics2D g2, 158 Rectangle2D dataArea, 159 CategoryPlot plot, 160 int rendererIndex, 161 PlotRenderingInfo info) { 162 163 CategoryItemRendererState state = super.initialise( 164 g2, dataArea, plot, rendererIndex, info 165 ); 166 calculateItemWidth(plot, dataArea, rendererIndex, state); 167 return state; 168 169 } 170 171 /** 172 * Calculates the bar width and stores it in the renderer state. 173 * 174 * @param plot the plot. 175 * @param dataArea the data area. 176 * @param rendererIndex the renderer index. 177 * @param state the renderer state. 178 */ 179 protected void calculateItemWidth(CategoryPlot plot, 180 Rectangle2D dataArea, 181 int rendererIndex, 182 CategoryItemRendererState state) { 183 184 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 185 CategoryDataset dataset = plot.getDataset(rendererIndex); 186 if (dataset != null) { 187 int columns = dataset.getColumnCount(); 188 int rows = dataset.getRowCount(); 189 double space = 0.0; 190 PlotOrientation orientation = plot.getOrientation(); 191 if (orientation == PlotOrientation.HORIZONTAL) { 192 space = dataArea.getHeight(); 193 } 194 else if (orientation == PlotOrientation.VERTICAL) { 195 space = dataArea.getWidth(); 196 } 197 double maxWidth = space * getMaxItemWidth(); 198 double categoryMargin = 0.0; 199 double currentItemMargin = 0.0; 200 if (columns > 1) { 201 categoryMargin = domainAxis.getCategoryMargin(); 202 } 203 if (rows > 1) { 204 currentItemMargin = getItemMargin(); 205 } 206 double used = space * (1 - domainAxis.getLowerMargin() 207 - domainAxis.getUpperMargin() 208 - categoryMargin - currentItemMargin); 209 if ((rows * columns) > 0) { 210 state.setBarWidth(Math.min(used / (rows * columns), maxWidth)); 211 } 212 else { 213 state.setBarWidth(Math.min(used, maxWidth)); 214 } 215 } 216 } 217 218 /** 219 * Calculates the coordinate of the first "side" of a bar. This will be 220 * the minimum x-coordinate for a vertical bar, and the minimum 221 * y-coordinate for a horizontal bar. 222 * 223 * @param plot the plot. 224 * @param orientation the plot orientation. 225 * @param dataArea the data area. 226 * @param domainAxis the domain axis. 227 * @param state the renderer state (has the bar width precalculated). 228 * @param row the row index. 229 * @param column the column index. 230 * 231 * @return The coordinate. 232 */ 233 protected double calculateBarW0(CategoryPlot plot, 234 PlotOrientation orientation, 235 Rectangle2D dataArea, 236 CategoryAxis domainAxis, 237 CategoryItemRendererState state, 238 int row, 239 int column) { 240 // calculate bar width... 241 double space = 0.0; 242 if (orientation == PlotOrientation.HORIZONTAL) { 243 space = dataArea.getHeight(); 244 } 245 else { 246 space = dataArea.getWidth(); 247 } 248 double barW0 = domainAxis.getCategoryStart( 249 column, getColumnCount(), dataArea, plot.getDomainAxisEdge() 250 ); 251 int seriesCount = getRowCount(); 252 int categoryCount = getColumnCount(); 253 if (seriesCount > 1) { 254 double seriesGap = space * getItemMargin() 255 / (categoryCount * (seriesCount - 1)); 256 double seriesW = calculateSeriesWidth( 257 space, domainAxis, categoryCount, seriesCount 258 ); 259 barW0 = barW0 + row * (seriesW + seriesGap) 260 + (seriesW / 2.0) - (state.getBarWidth() / 2.0); 261 } 262 else { 263 barW0 = domainAxis.getCategoryMiddle( 264 column, getColumnCount(), dataArea, 265 plot.getDomainAxisEdge() 266 ) 267 - state.getBarWidth() / 2.0; 268 } 269 return barW0; 270 } 271 272 /** 273 * Draws the bar for a single (series, category) data item. 274 * 275 * @param g2 the graphics device. 276 * @param state the renderer state. 277 * @param dataArea the data area. 278 * @param plot the plot. 279 * @param domainAxis the domain axis. 280 * @param rangeAxis the range axis. 281 * @param dataset the dataset. 282 * @param row the row index (zero-based). 283 * @param column the column index (zero-based). 284 * @param pass the pass index. 285 */ 286 public void drawItem(Graphics2D g2, 287 CategoryItemRendererState state, 288 Rectangle2D dataArea, 289 CategoryPlot plot, 290 CategoryAxis domainAxis, 291 ValueAxis rangeAxis, 292 CategoryDataset dataset, 293 int row, 294 int column, 295 int pass) { 296 297 // nothing is drawn for null values... 298 Number dataValue = dataset.getValue(row, column); 299 if (dataValue == null) { 300 return; 301 } 302 303 double value = dataValue.doubleValue(); 304 305 PlotOrientation orientation = plot.getOrientation(); 306 double barW0 = calculateBarW0( 307 plot, orientation, dataArea, domainAxis, state, row, column 308 ); 309 RectangleEdge edge = plot.getRangeAxisEdge(); 310 double barL = rangeAxis.valueToJava2D(value, dataArea, edge); 311 312 // draw the bar... 313 Line2D line = null; 314 double x = 0.0; 315 double y = 0.0; 316 if (orientation == PlotOrientation.HORIZONTAL) { 317 x = barL; 318 y = barW0 + state.getBarWidth() / 2.0; 319 line = new Line2D.Double( 320 barL, barW0, barL, barW0 + state.getBarWidth() 321 ); 322 } 323 else { 324 x = barW0 + state.getBarWidth() / 2.0; 325 y = barL; 326 line = new Line2D.Double( 327 barW0, barL, barW0 + state.getBarWidth(), barL 328 ); 329 } 330 Stroke itemStroke = getItemStroke(row, column); 331 Paint itemPaint = getItemPaint(row, column); 332 g2.setStroke(itemStroke); 333 g2.setPaint(itemPaint); 334 g2.draw(line); 335 336 CategoryItemLabelGenerator generator 337 = getItemLabelGenerator(row, column); 338 if (generator != null && isItemLabelVisible(row, column)) { 339 drawItemLabel( 340 g2, orientation, dataset, row, column, x, y, (value < 0.0) 341 ); 342 } 343 344 // collect entity and tool tip information... 345 if (state.getInfo() != null) { 346 EntityCollection entities = state.getEntityCollection(); 347 if (entities != null) { 348 String tip = null; 349 CategoryToolTipGenerator tipster 350 = getToolTipGenerator(row, column); 351 if (tipster != null) { 352 tip = tipster.generateToolTip(dataset, row, column); 353 } 354 String url = null; 355 if (getItemURLGenerator(row, column) != null) { 356 url = getItemURLGenerator(row, column).generateURL( 357 dataset, row, column 358 ); 359 } 360 CategoryItemEntity entity = new CategoryItemEntity( 361 line.getBounds(), tip, url, dataset, row, 362 dataset.getColumnKey(column), column 363 ); 364 entities.add(entity); 365 } 366 367 } 368 369 } 370 371 /** 372 * Calculates the available space for each series. 373 * 374 * @param space the space along the entire axis (in Java2D units). 375 * @param axis the category axis. 376 * @param categories the number of categories. 377 * @param series the number of series. 378 * 379 * @return The width of one series. 380 */ 381 protected double calculateSeriesWidth(double space, CategoryAxis axis, 382 int categories, int series) { 383 double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 384 - axis.getUpperMargin(); 385 if (categories > 1) { 386 factor = factor - axis.getCategoryMargin(); 387 } 388 return (space * factor) / (categories * series); 389 } 390 391 /** 392 * Tests an object for equality with this instance. 393 * 394 * @param obj the object. 395 * 396 * @return A boolean. 397 */ 398 public boolean equals(Object obj) { 399 if (obj == this) { 400 return true; 401 } 402 if (!(obj instanceof LevelRenderer)) { 403 return false; 404 } 405 if (!super.equals(obj)) { 406 return false; 407 } 408 LevelRenderer that = (LevelRenderer) obj; 409 if (this.itemMargin != that.itemMargin) { 410 return false; 411 } 412 if (this.maxItemWidth != that.maxItemWidth) { 413 return false; 414 } 415 return true; 416 } 417 418 }