001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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, rangeAxis, 199 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, row, data.getColumnKey(column), 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, row, data.getColumnKey(column), column); 494 entities.add(entity); 495 } 496 } 497 } 498 499 }