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