001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-2008, 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 implements Serializable {
080
081 /** For serialization. */
082 private static final long serialVersionUID = -8716572894780469487L;
083
084 /** A list of the width of each series bar. */
085 protected ObjectList seriesBarWidthList;
086
087 /**
088 * Default constructor.
089 */
090 public LayeredBarRenderer() {
091 super();
092 this.seriesBarWidthList = new ObjectList();
093 }
094
095 /**
096 * Returns the bar width for a series, or <code>Double.NaN</code> if no
097 * width has been set.
098 *
099 * @param series the series index (zero based).
100 *
101 * @return The width for the series (1.0=100%, it is the maximum).
102 */
103 public double getSeriesBarWidth(int series) {
104 double result = Double.NaN;
105 Number n = (Number) this.seriesBarWidthList.get(series);
106 if (n != null) {
107 result = n.doubleValue();
108 }
109 return result;
110 }
111
112 /**
113 * Sets the width of the bars of a series.
114 *
115 * @param series the series index (zero based).
116 * @param width the width of the series bar in percentage (1.0=100%, it is
117 * the maximum).
118 */
119 public void setSeriesBarWidth(int series, double width) {
120 this.seriesBarWidthList.set(series, new Double(width));
121 }
122
123 /**
124 * Calculates the bar width and stores it in the renderer state.
125 *
126 * @param plot the plot.
127 * @param dataArea the data area.
128 * @param rendererIndex the renderer index.
129 * @param state the renderer state.
130 */
131 protected void calculateBarWidth(CategoryPlot plot,
132 Rectangle2D dataArea,
133 int rendererIndex,
134 CategoryItemRendererState state) {
135
136 // calculate the bar width - this calculation differs from the
137 // BarRenderer calculation because the bars are layered on top of one
138 // another, so there is effectively only one bar per category for
139 // the purpose of the bar width calculation
140 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
141 CategoryDataset dataset = plot.getDataset(rendererIndex);
142 if (dataset != null) {
143 int columns = dataset.getColumnCount();
144 int rows = dataset.getRowCount();
145 double space = 0.0;
146 PlotOrientation orientation = plot.getOrientation();
147 if (orientation == PlotOrientation.HORIZONTAL) {
148 space = dataArea.getHeight();
149 }
150 else if (orientation == PlotOrientation.VERTICAL) {
151 space = dataArea.getWidth();
152 }
153 double maxWidth = space * getMaximumBarWidth();
154 double categoryMargin = 0.0;
155 if (columns > 1) {
156 categoryMargin = domainAxis.getCategoryMargin();
157 }
158 double used = space * (1 - domainAxis.getLowerMargin()
159 - domainAxis.getUpperMargin() - categoryMargin);
160 if ((rows * columns) > 0) {
161 state.setBarWidth(Math.min(used / (dataset.getColumnCount()),
162 maxWidth));
163 }
164 else {
165 state.setBarWidth(Math.min(used, maxWidth));
166 }
167 }
168 }
169
170 /**
171 * Draws the bar for one item in the dataset.
172 *
173 * @param g2 the graphics device.
174 * @param state the renderer state.
175 * @param dataArea the plot area.
176 * @param plot the plot.
177 * @param domainAxis the domain (category) axis.
178 * @param rangeAxis the range (value) axis.
179 * @param data the data.
180 * @param row the row index (zero-based).
181 * @param column the column index (zero-based).
182 * @param pass the pass index.
183 */
184 public void drawItem(Graphics2D g2,
185 CategoryItemRendererState state,
186 Rectangle2D dataArea,
187 CategoryPlot plot,
188 CategoryAxis domainAxis,
189 ValueAxis rangeAxis,
190 CategoryDataset data,
191 int row,
192 int column,
193 int pass) {
194
195 PlotOrientation orientation = plot.getOrientation();
196 if (orientation == PlotOrientation.HORIZONTAL) {
197 drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
198 rangeAxis, data, row, column);
199 }
200 else if (orientation == PlotOrientation.VERTICAL) {
201 drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
202 data, row, column);
203 }
204
205 }
206
207 /**
208 * Draws the bar for a single (series, category) data item.
209 *
210 * @param g2 the graphics device.
211 * @param state the renderer state.
212 * @param dataArea the data area.
213 * @param plot the plot.
214 * @param domainAxis the domain axis.
215 * @param rangeAxis the range axis.
216 * @param data the data.
217 * @param row the row index (zero-based).
218 * @param column the column index (zero-based).
219 */
220 protected void drawHorizontalItem(Graphics2D g2,
221 CategoryItemRendererState state,
222 Rectangle2D dataArea,
223 CategoryPlot plot,
224 CategoryAxis domainAxis,
225 ValueAxis rangeAxis,
226 CategoryDataset data,
227 int row,
228 int column) {
229
230 // nothing is drawn for null values...
231 Number dataValue = data.getValue(row, column);
232 if (dataValue == null) {
233 return;
234 }
235
236 // X
237 double value = dataValue.doubleValue();
238 double base = 0.0;
239 double lclip = getLowerClip();
240 double uclip = getUpperClip();
241 if (uclip <= 0.0) { // cases 1, 2, 3 and 4
242 if (value >= uclip) {
243 return; // bar is not visible
244 }
245 base = uclip;
246 if (value <= lclip) {
247 value = lclip;
248 }
249 }
250 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
251 if (value >= uclip) {
252 value = uclip;
253 }
254 else {
255 if (value <= lclip) {
256 value = lclip;
257 }
258 }
259 }
260 else { // cases 9, 10, 11 and 12
261 if (value <= lclip) {
262 return; // bar is not visible
263 }
264 base = lclip;
265 if (value >= uclip) {
266 value = uclip;
267 }
268 }
269
270 RectangleEdge edge = plot.getRangeAxisEdge();
271 double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
272 double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
273 double rectX = Math.min(transX1, transX2);
274 double rectWidth = Math.abs(transX2 - transX1);
275
276 // Y
277 double rectY = domainAxis.getCategoryMiddle(column, getColumnCount(),
278 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
279
280 int seriesCount = getRowCount();
281
282 // draw the bar...
283 double shift = 0.0;
284 double rectHeight = 0.0;
285 double widthFactor = 1.0;
286 double seriesBarWidth = getSeriesBarWidth(row);
287 if (!Double.isNaN(seriesBarWidth)) {
288 widthFactor = seriesBarWidth;
289 }
290 rectHeight = widthFactor * state.getBarWidth();
291 rectY = rectY + (1 - widthFactor) * state.getBarWidth() / 2.0;
292 if (seriesCount > 1) {
293 shift = rectHeight * 0.20 / (seriesCount - 1);
294 }
295
296 Rectangle2D bar = new Rectangle2D.Double(rectX,
297 (rectY + ((seriesCount - 1 - row) * shift)), rectWidth,
298 (rectHeight - (seriesCount - 1 - row) * shift * 2));
299
300 Paint itemPaint = getItemPaint(row, column);
301 GradientPaintTransformer t = getGradientPaintTransformer();
302 if (t != null && itemPaint instanceof GradientPaint) {
303 itemPaint = t.transform((GradientPaint) itemPaint, bar);
304 }
305 g2.setPaint(itemPaint);
306 g2.fill(bar);
307
308 // draw the outline...
309 if (isDrawBarOutline()
310 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
311 Stroke stroke = getItemOutlineStroke(row, column);
312 Paint paint = getItemOutlinePaint(row, column);
313 if (stroke != null && paint != null) {
314 g2.setStroke(stroke);
315 g2.setPaint(paint);
316 g2.draw(bar);
317 }
318 }
319
320 CategoryItemLabelGenerator generator
321 = getItemLabelGenerator(row, column);
322 if (generator != null && isItemLabelVisible(row, column)) {
323 drawItemLabel(g2, data, row, column, plot, generator, bar,
324 (transX1 > transX2));
325 }
326
327 // collect entity and tool tip information...
328 if (state.getInfo() != null) {
329 EntityCollection entities = state.getEntityCollection();
330 if (entities != null) {
331 String tip = null;
332 CategoryToolTipGenerator tipster
333 = getToolTipGenerator(row, column);
334 if (tipster != null) {
335 tip = tipster.generateToolTip(data, row, column);
336 }
337 String url = null;
338 if (getItemURLGenerator(row, column) != null) {
339 url = getItemURLGenerator(row, column).generateURL(data,
340 row, column);
341 }
342 CategoryItemEntity entity = new CategoryItemEntity(bar, tip,
343 url, data, data.getRowKey(row),
344 data.getColumnKey(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, data.getRowKey(row),
494 data.getColumnKey(column));
495 entities.add(entity);
496 }
497 }
498 }
499
500 }