001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * LayeredBarRenderer.java 029 * ----------------------- 030 * (C) Copyright 2003-2008, by Arnaud Lelievre and Contributors. 031 * 032 * Original Author: Arnaud Lelievre (for Garden); 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Zoheb Borbora; 035 * 036 * Changes 037 * ------- 038 * 28-Aug-2003 : Version 1 (AL); 039 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 040 * 07-Oct-2003 : Added renderer state (DG); 041 * 21-Oct-2003 : Bar width moved to renderer state (DG); 042 * 05-Nov-2004 : Modified drawItem() signature (DG); 043 * 20-Apr-2005 : Renamed CategoryLabelGenerator 044 * --> CategoryItemLabelGenerator (DG); 045 * 17-Nov-2005 : Added support for gradient paint (DG); 046 * ------------- JFREECHART 1.0.x --------------------------------------------- 047 * 18-Aug-2006 : Fixed the bar width calculation to respect the maximum bar 048 * width setting (thanks to Zoheb Borbora) (DG); 049 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 050 * 051 */ 052 053 package org.jfree.chart.renderer.category; 054 055 import java.awt.GradientPaint; 056 import java.awt.Graphics2D; 057 import java.awt.Paint; 058 import java.awt.Stroke; 059 import java.awt.geom.Rectangle2D; 060 import java.io.Serializable; 061 062 import org.jfree.chart.axis.CategoryAxis; 063 import org.jfree.chart.axis.ValueAxis; 064 import org.jfree.chart.entity.CategoryItemEntity; 065 import org.jfree.chart.entity.EntityCollection; 066 import org.jfree.chart.labels.CategoryItemLabelGenerator; 067 import org.jfree.chart.labels.CategoryToolTipGenerator; 068 import org.jfree.chart.plot.CategoryPlot; 069 import org.jfree.chart.plot.PlotOrientation; 070 import org.jfree.data.category.CategoryDataset; 071 import org.jfree.ui.GradientPaintTransformer; 072 import org.jfree.ui.RectangleEdge; 073 import org.jfree.util.ObjectList; 074 075 /** 076 * A {@link CategoryItemRenderer} that represents data using bars which are 077 * superimposed. 078 */ 079 public class LayeredBarRenderer extends BarRenderer implements Serializable { 080 081 /** For serialization. */ 082 private static final long serialVersionUID = -8716572894780469487L; 083 084 /** A list of the width of each series bar. */ 085 protected ObjectList seriesBarWidthList; 086 087 /** 088 * Default constructor. 089 */ 090 public LayeredBarRenderer() { 091 super(); 092 this.seriesBarWidthList = new ObjectList(); 093 } 094 095 /** 096 * Returns the bar width for a series, or <code>Double.NaN</code> if no 097 * width has been set. 098 * 099 * @param series the series index (zero based). 100 * 101 * @return The width for the series (1.0=100%, it is the maximum). 102 */ 103 public double getSeriesBarWidth(int series) { 104 double result = Double.NaN; 105 Number n = (Number) this.seriesBarWidthList.get(series); 106 if (n != null) { 107 result = n.doubleValue(); 108 } 109 return result; 110 } 111 112 /** 113 * Sets the width of the bars of a series. 114 * 115 * @param series the series index (zero based). 116 * @param width the width of the series bar in percentage (1.0=100%, it is 117 * the maximum). 118 */ 119 public void setSeriesBarWidth(int series, double width) { 120 this.seriesBarWidthList.set(series, new Double(width)); 121 } 122 123 /** 124 * Calculates the bar width and stores it in the renderer state. 125 * 126 * @param plot the plot. 127 * @param dataArea the data area. 128 * @param rendererIndex the renderer index. 129 * @param state the renderer state. 130 */ 131 protected void calculateBarWidth(CategoryPlot plot, 132 Rectangle2D dataArea, 133 int rendererIndex, 134 CategoryItemRendererState state) { 135 136 // calculate the bar width - this calculation differs from the 137 // BarRenderer calculation because the bars are layered on top of one 138 // another, so there is effectively only one bar per category for 139 // the purpose of the bar width calculation 140 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 141 CategoryDataset dataset = plot.getDataset(rendererIndex); 142 if (dataset != null) { 143 int columns = dataset.getColumnCount(); 144 int rows = dataset.getRowCount(); 145 double space = 0.0; 146 PlotOrientation orientation = plot.getOrientation(); 147 if (orientation == PlotOrientation.HORIZONTAL) { 148 space = dataArea.getHeight(); 149 } 150 else if (orientation == PlotOrientation.VERTICAL) { 151 space = dataArea.getWidth(); 152 } 153 double maxWidth = space * getMaximumBarWidth(); 154 double categoryMargin = 0.0; 155 if (columns > 1) { 156 categoryMargin = domainAxis.getCategoryMargin(); 157 } 158 double used = space * (1 - domainAxis.getLowerMargin() 159 - domainAxis.getUpperMargin() - categoryMargin); 160 if ((rows * columns) > 0) { 161 state.setBarWidth(Math.min(used / (dataset.getColumnCount()), 162 maxWidth)); 163 } 164 else { 165 state.setBarWidth(Math.min(used, maxWidth)); 166 } 167 } 168 } 169 170 /** 171 * Draws the bar for one item in the dataset. 172 * 173 * @param g2 the graphics device. 174 * @param state the renderer state. 175 * @param dataArea the plot area. 176 * @param plot the plot. 177 * @param domainAxis the domain (category) axis. 178 * @param rangeAxis the range (value) axis. 179 * @param data the data. 180 * @param row the row index (zero-based). 181 * @param column the column index (zero-based). 182 * @param pass the pass index. 183 */ 184 public void drawItem(Graphics2D g2, 185 CategoryItemRendererState state, 186 Rectangle2D dataArea, 187 CategoryPlot plot, 188 CategoryAxis domainAxis, 189 ValueAxis rangeAxis, 190 CategoryDataset data, 191 int row, 192 int column, 193 int pass) { 194 195 PlotOrientation orientation = plot.getOrientation(); 196 if (orientation == PlotOrientation.HORIZONTAL) { 197 drawHorizontalItem(g2, state, dataArea, plot, domainAxis, 198 rangeAxis, data, row, column); 199 } 200 else if (orientation == PlotOrientation.VERTICAL) { 201 drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 202 data, row, column); 203 } 204 205 } 206 207 /** 208 * Draws the bar for a single (series, category) data item. 209 * 210 * @param g2 the graphics device. 211 * @param state the renderer state. 212 * @param dataArea the data area. 213 * @param plot the plot. 214 * @param domainAxis the domain axis. 215 * @param rangeAxis the range axis. 216 * @param data the data. 217 * @param row the row index (zero-based). 218 * @param column the column index (zero-based). 219 */ 220 protected void drawHorizontalItem(Graphics2D g2, 221 CategoryItemRendererState state, 222 Rectangle2D dataArea, 223 CategoryPlot plot, 224 CategoryAxis domainAxis, 225 ValueAxis rangeAxis, 226 CategoryDataset data, 227 int row, 228 int column) { 229 230 // nothing is drawn for null values... 231 Number dataValue = data.getValue(row, column); 232 if (dataValue == null) { 233 return; 234 } 235 236 // X 237 double value = dataValue.doubleValue(); 238 double base = 0.0; 239 double lclip = getLowerClip(); 240 double uclip = getUpperClip(); 241 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 242 if (value >= uclip) { 243 return; // bar is not visible 244 } 245 base = uclip; 246 if (value <= lclip) { 247 value = lclip; 248 } 249 } 250 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 251 if (value >= uclip) { 252 value = uclip; 253 } 254 else { 255 if (value <= lclip) { 256 value = lclip; 257 } 258 } 259 } 260 else { // cases 9, 10, 11 and 12 261 if (value <= lclip) { 262 return; // bar is not visible 263 } 264 base = lclip; 265 if (value >= uclip) { 266 value = uclip; 267 } 268 } 269 270 RectangleEdge edge = plot.getRangeAxisEdge(); 271 double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge); 272 double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge); 273 double rectX = Math.min(transX1, transX2); 274 double rectWidth = Math.abs(transX2 - transX1); 275 276 // Y 277 double rectY = domainAxis.getCategoryMiddle(column, getColumnCount(), 278 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0; 279 280 int seriesCount = getRowCount(); 281 282 // draw the bar... 283 double shift = 0.0; 284 double rectHeight = 0.0; 285 double widthFactor = 1.0; 286 double seriesBarWidth = getSeriesBarWidth(row); 287 if (!Double.isNaN(seriesBarWidth)) { 288 widthFactor = seriesBarWidth; 289 } 290 rectHeight = widthFactor * state.getBarWidth(); 291 rectY = rectY + (1 - widthFactor) * state.getBarWidth() / 2.0; 292 if (seriesCount > 1) { 293 shift = rectHeight * 0.20 / (seriesCount - 1); 294 } 295 296 Rectangle2D bar = new Rectangle2D.Double(rectX, 297 (rectY + ((seriesCount - 1 - row) * shift)), rectWidth, 298 (rectHeight - (seriesCount - 1 - row) * shift * 2)); 299 300 Paint itemPaint = getItemPaint(row, column); 301 GradientPaintTransformer t = getGradientPaintTransformer(); 302 if (t != null && itemPaint instanceof GradientPaint) { 303 itemPaint = t.transform((GradientPaint) itemPaint, bar); 304 } 305 g2.setPaint(itemPaint); 306 g2.fill(bar); 307 308 // draw the outline... 309 if (isDrawBarOutline() 310 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 311 Stroke stroke = getItemOutlineStroke(row, column); 312 Paint paint = getItemOutlinePaint(row, column); 313 if (stroke != null && paint != null) { 314 g2.setStroke(stroke); 315 g2.setPaint(paint); 316 g2.draw(bar); 317 } 318 } 319 320 CategoryItemLabelGenerator generator 321 = getItemLabelGenerator(row, column); 322 if (generator != null && isItemLabelVisible(row, column)) { 323 drawItemLabel(g2, data, row, column, plot, generator, bar, 324 (transX1 > transX2)); 325 } 326 327 // collect entity and tool tip information... 328 if (state.getInfo() != null) { 329 EntityCollection entities = state.getEntityCollection(); 330 if (entities != null) { 331 String tip = null; 332 CategoryToolTipGenerator tipster 333 = getToolTipGenerator(row, column); 334 if (tipster != null) { 335 tip = tipster.generateToolTip(data, row, column); 336 } 337 String url = null; 338 if (getItemURLGenerator(row, column) != null) { 339 url = getItemURLGenerator(row, column).generateURL(data, 340 row, column); 341 } 342 CategoryItemEntity entity = new CategoryItemEntity(bar, tip, 343 url, data, data.getRowKey(row), 344 data.getColumnKey(column)); 345 entities.add(entity); 346 } 347 } 348 } 349 350 /** 351 * Draws the bar for a single (series, category) data item. 352 * 353 * @param g2 the graphics device. 354 * @param state the renderer state. 355 * @param dataArea the data area. 356 * @param plot the plot. 357 * @param domainAxis the domain axis. 358 * @param rangeAxis the range axis. 359 * @param data the data. 360 * @param row the row index (zero-based). 361 * @param column the column index (zero-based). 362 */ 363 protected void drawVerticalItem(Graphics2D g2, 364 CategoryItemRendererState state, 365 Rectangle2D dataArea, 366 CategoryPlot plot, 367 CategoryAxis domainAxis, 368 ValueAxis rangeAxis, 369 CategoryDataset data, 370 int row, 371 int column) { 372 373 // nothing is drawn for null values... 374 Number dataValue = data.getValue(row, column); 375 if (dataValue == null) { 376 return; 377 } 378 379 // BAR X 380 double rectX = domainAxis.getCategoryMiddle(column, getColumnCount(), 381 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0; 382 383 int seriesCount = getRowCount(); 384 385 // BAR Y 386 double value = dataValue.doubleValue(); 387 double base = 0.0; 388 double lclip = getLowerClip(); 389 double uclip = getUpperClip(); 390 391 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 392 if (value >= uclip) { 393 return; // bar is not visible 394 } 395 base = uclip; 396 if (value <= lclip) { 397 value = lclip; 398 } 399 } 400 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 401 if (value >= uclip) { 402 value = uclip; 403 } 404 else { 405 if (value <= lclip) { 406 value = lclip; 407 } 408 } 409 } 410 else { // cases 9, 10, 11 and 12 411 if (value <= lclip) { 412 return; // bar is not visible 413 } 414 base = getLowerClip(); 415 if (value >= uclip) { 416 value = uclip; 417 } 418 } 419 420 RectangleEdge edge = plot.getRangeAxisEdge(); 421 double transY1 = rangeAxis.valueToJava2D(base, dataArea, edge); 422 double transY2 = rangeAxis.valueToJava2D(value, dataArea, edge); 423 double rectY = Math.min(transY2, transY1); 424 425 double rectWidth = state.getBarWidth(); 426 double rectHeight = Math.abs(transY2 - transY1); 427 428 // draw the bar... 429 double shift = 0.0; 430 rectWidth = 0.0; 431 double widthFactor = 1.0; 432 double seriesBarWidth = getSeriesBarWidth(row); 433 if (!Double.isNaN(seriesBarWidth)) { 434 widthFactor = seriesBarWidth; 435 } 436 rectWidth = widthFactor * state.getBarWidth(); 437 rectX = rectX + (1 - widthFactor) * state.getBarWidth() / 2.0; 438 if (seriesCount > 1) { 439 // needs to be improved !!! 440 shift = rectWidth * 0.20 / (seriesCount - 1); 441 } 442 443 Rectangle2D bar = new Rectangle2D.Double( 444 (rectX + ((seriesCount - 1 - row) * shift)), rectY, 445 (rectWidth - (seriesCount - 1 - row) * shift * 2), rectHeight); 446 Paint itemPaint = getItemPaint(row, column); 447 GradientPaintTransformer t = getGradientPaintTransformer(); 448 if (t != null && itemPaint instanceof GradientPaint) { 449 itemPaint = t.transform((GradientPaint) itemPaint, bar); 450 } 451 g2.setPaint(itemPaint); 452 g2.fill(bar); 453 454 // draw the outline... 455 if (isDrawBarOutline() 456 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 457 Stroke stroke = getItemOutlineStroke(row, column); 458 Paint paint = getItemOutlinePaint(row, column); 459 if (stroke != null && paint != null) { 460 g2.setStroke(stroke); 461 g2.setPaint(paint); 462 g2.draw(bar); 463 } 464 } 465 466 // draw the item labels if there are any... 467 double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge); 468 double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge); 469 470 CategoryItemLabelGenerator generator 471 = getItemLabelGenerator(row, column); 472 if (generator != null && isItemLabelVisible(row, column)) { 473 drawItemLabel(g2, data, row, column, plot, generator, bar, 474 (transX1 > transX2)); 475 } 476 477 // collect entity and tool tip information... 478 if (state.getInfo() != null) { 479 EntityCollection entities = state.getEntityCollection(); 480 if (entities != null) { 481 String tip = null; 482 CategoryToolTipGenerator tipster 483 = getToolTipGenerator(row, column); 484 if (tipster != null) { 485 tip = tipster.generateToolTip(data, row, column); 486 } 487 String url = null; 488 if (getItemURLGenerator(row, column) != null) { 489 url = getItemURLGenerator(row, column).generateURL( 490 data, row, column); 491 } 492 CategoryItemEntity entity = new CategoryItemEntity(bar, tip, 493 url, data, data.getRowKey(row), 494 data.getColumnKey(column)); 495 entities.add(entity); 496 } 497 } 498 } 499 500 }