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