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