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