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 }