001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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 * StackedBarRenderer3D.java
029 * -------------------------
030 * (C) Copyright 2000-2007, by Serge V. Grachov and Contributors.
031 *
032 * Original Author: Serge V. Grachov;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 * Richard Atkinson;
035 * Christian W. Zuckschwerdt;
036 * Max Herfort (patch 1459313);
037 *
038 * $Id: StackedBarRenderer3D.java,v 1.8.2.9 2007/05/08 15:04:55 mungady Exp $
039 *
040 * Changes
041 * -------
042 * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
043 * 15-Nov-2001 : Modified to allow for null data values (DG);
044 * 13-Dec-2001 : Added tooltips (DG);
045 * 15-Feb-2002 : Added isStacked() method (DG);
046 * 24-May-2002 : Incorporated tooltips into chart entities (DG);
047 * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
048 * 25-Jun-2002 : Removed redundant imports (DG);
049 * 26-Jun-2002 : Small change to entity (DG);
050 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
051 * for HTML image maps (RA);
052 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
053 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
054 * CategoryToolTipGenerator interface (DG);
055 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
056 * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
057 * 17-Jan-2003 : Moved plot classes to a separate package (DG);
058 * 25-Mar-2003 : Implemented Serializable (DG);
059 * 01-May-2003 : Added default constructor (bug 726235) and fixed bug
060 * 726260) (DG);
061 * 13-May-2003 : Renamed StackedVerticalBarRenderer3D
062 * --> StackedBarRenderer3D (DG);
063 * 30-Jul-2003 : Modified entity constructor (CZ);
064 * 07-Oct-2003 : Added renderer state (DG);
065 * 21-Nov-2003 : Added a new constructor (DG);
066 * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
067 * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
068 * 05-Nov-2004 : Modified drawItem() signature (DG);
069 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
070 * 18-Mar-2005 : Override for getPassCount() method (DG);
071 * 20-Apr-2005 : Renamed CategoryLabelGenerator
072 * --> CategoryItemLabelGenerator (DG);
073 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
074 * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
075 * ------------- JFREECHART 1.0.x ---------------------------------------------
076 * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
077 * by Max Herfort (DG);
078 * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
079 * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList()
080 * method (DG);
081 * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
082 * see bug report 1599652 (DG);
083 * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and 1713474
084 * (shading) (DG);
085 *
086 */
087
088 package org.jfree.chart.renderer.category;
089
090 import java.awt.Color;
091 import java.awt.Graphics2D;
092 import java.awt.Paint;
093 import java.awt.Shape;
094 import java.awt.geom.GeneralPath;
095 import java.awt.geom.Point2D;
096 import java.awt.geom.Rectangle2D;
097 import java.io.Serializable;
098 import java.util.ArrayList;
099 import java.util.List;
100
101 import org.jfree.chart.axis.CategoryAxis;
102 import org.jfree.chart.axis.ValueAxis;
103 import org.jfree.chart.entity.EntityCollection;
104 import org.jfree.chart.event.RendererChangeEvent;
105 import org.jfree.chart.labels.CategoryItemLabelGenerator;
106 import org.jfree.chart.plot.CategoryPlot;
107 import org.jfree.chart.plot.PlotOrientation;
108 import org.jfree.data.DataUtilities;
109 import org.jfree.data.Range;
110 import org.jfree.data.category.CategoryDataset;
111 import org.jfree.data.general.DatasetUtilities;
112 import org.jfree.util.BooleanUtilities;
113 import org.jfree.util.PublicCloneable;
114
115 /**
116 * Renders stacked bars with 3D-effect, for use with the
117 * {@link org.jfree.chart.plot.CategoryPlot} class.
118 */
119 public class StackedBarRenderer3D extends BarRenderer3D
120 implements Cloneable, PublicCloneable,
121 Serializable {
122
123 /** For serialization. */
124 private static final long serialVersionUID = -5832945916493247123L;
125
126 /** A flag that controls whether the bars display values or percentages. */
127 private boolean renderAsPercentages;
128
129 /**
130 * Creates a new renderer with no tool tip generator and no URL generator.
131 * <P>
132 * The defaults (no tool tip or URL generators) have been chosen to
133 * minimise the processing required to generate a default chart. If you
134 * require tool tips or URLs, then you can easily add the required
135 * generators.
136 */
137 public StackedBarRenderer3D() {
138 this(false);
139 }
140
141 /**
142 * Constructs a new renderer with the specified '3D effect'.
143 *
144 * @param xOffset the x-offset for the 3D effect.
145 * @param yOffset the y-offset for the 3D effect.
146 */
147 public StackedBarRenderer3D(double xOffset, double yOffset) {
148 super(xOffset, yOffset);
149 }
150
151 /**
152 * Creates a new renderer.
153 *
154 * @param renderAsPercentages a flag that controls whether the data values
155 * are rendered as percentages.
156 *
157 * @since 1.0.2
158 */
159 public StackedBarRenderer3D(boolean renderAsPercentages) {
160 super();
161 this.renderAsPercentages = renderAsPercentages;
162 }
163
164 /**
165 * Constructs a new renderer with the specified '3D effect'.
166 *
167 * @param xOffset the x-offset for the 3D effect.
168 * @param yOffset the y-offset for the 3D effect.
169 * @param renderAsPercentages a flag that controls whether the data values
170 * are rendered as percentages.
171 *
172 * @since 1.0.2
173 */
174 public StackedBarRenderer3D(double xOffset, double yOffset,
175 boolean renderAsPercentages) {
176 super(xOffset, yOffset);
177 this.renderAsPercentages = renderAsPercentages;
178 }
179
180 /**
181 * Returns <code>true</code> if the renderer displays each item value as
182 * a percentage (so that the stacked bars add to 100%), and
183 * <code>false</code> otherwise.
184 *
185 * @return A boolean.
186 *
187 * @since 1.0.2
188 */
189 public boolean getRenderAsPercentages() {
190 return this.renderAsPercentages;
191 }
192
193 /**
194 * Sets the flag that controls whether the renderer displays each item
195 * value as a percentage (so that the stacked bars add to 100%), and sends
196 * a {@link RendererChangeEvent} to all registered listeners.
197 *
198 * @param asPercentages the flag.
199 *
200 * @since 1.0.2
201 */
202 public void setRenderAsPercentages(boolean asPercentages) {
203 this.renderAsPercentages = asPercentages;
204 notifyListeners(new RendererChangeEvent(this));
205 }
206
207 /**
208 * Returns the range of values the renderer requires to display all the
209 * items from the specified dataset.
210 *
211 * @param dataset the dataset (<code>null</code> not permitted).
212 *
213 * @return The range (or <code>null</code> if the dataset is empty).
214 */
215 public Range findRangeBounds(CategoryDataset dataset) {
216 if (this.renderAsPercentages) {
217 return new Range(0.0, 1.0);
218 }
219 else {
220 return DatasetUtilities.findStackedRangeBounds(dataset);
221 }
222 }
223
224 /**
225 * Calculates the bar width and stores it in the renderer state.
226 *
227 * @param plot the plot.
228 * @param dataArea the data area.
229 * @param rendererIndex the renderer index.
230 * @param state the renderer state.
231 */
232 protected void calculateBarWidth(CategoryPlot plot,
233 Rectangle2D dataArea,
234 int rendererIndex,
235 CategoryItemRendererState state) {
236
237 // calculate the bar width
238 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
239 CategoryDataset data = plot.getDataset(rendererIndex);
240 if (data != null) {
241 PlotOrientation orientation = plot.getOrientation();
242 double space = 0.0;
243 if (orientation == PlotOrientation.HORIZONTAL) {
244 space = dataArea.getHeight();
245 }
246 else if (orientation == PlotOrientation.VERTICAL) {
247 space = dataArea.getWidth();
248 }
249 double maxWidth = space * getMaximumBarWidth();
250 int columns = data.getColumnCount();
251 double categoryMargin = 0.0;
252 if (columns > 1) {
253 categoryMargin = domainAxis.getCategoryMargin();
254 }
255
256 double used = space * (1 - domainAxis.getLowerMargin()
257 - domainAxis.getUpperMargin()
258 - categoryMargin);
259 if (columns > 0) {
260 state.setBarWidth(Math.min(used / columns, maxWidth));
261 }
262 else {
263 state.setBarWidth(Math.min(used, maxWidth));
264 }
265 }
266
267 }
268
269 /**
270 * Returns a list containing the stacked values for the specified series
271 * in the given dataset, plus the supplied base value.
272 *
273 * @param dataset the dataset (<code>null</code> not permitted).
274 * @param category the category key (<code>null</code> not permitted).
275 * @param base the base value.
276 * @param asPercentages a flag that controls whether the values in the
277 * list are converted to percentages of the total.
278 *
279 * @return The value list.
280 *
281 * @since 1.0.4
282 */
283 protected static List createStackedValueList(CategoryDataset dataset,
284 Comparable category, double base, boolean asPercentages) {
285
286 List result = new ArrayList();
287 double posBase = base;
288 double negBase = base;
289 double total = 0.0;
290 if (asPercentages) {
291 total = DataUtilities.calculateColumnTotal(dataset,
292 dataset.getColumnIndex(category));
293 }
294
295 int baseIndex = -1;
296 int seriesCount = dataset.getRowCount();
297 for (int s = 0; s < seriesCount; s++) {
298 Number n = dataset.getValue(dataset.getRowKey(s), category);
299 if (n == null) {
300 continue;
301 }
302 double v = n.doubleValue();
303 if (asPercentages) {
304 v = v / total;
305 }
306 if (v >= 0.0) {
307 if (baseIndex < 0) {
308 result.add(new Object[] {null, new Double(base)});
309 baseIndex = 0;
310 }
311 posBase = posBase + v;
312 result.add(new Object[] {new Integer(s), new Double(posBase)});
313 }
314 else if (v < 0.0) {
315 if (baseIndex < 0) {
316 result.add(new Object[] {null, new Double(base)});
317 baseIndex = 0;
318 }
319 negBase = negBase + v; // '+' because v is negative
320 result.add(0, new Object[] {new Integer(-s),
321 new Double(negBase)});
322 baseIndex++;
323 }
324 }
325 return result;
326
327 }
328
329 /**
330 * Draws the visual representation of one data item from the chart (in
331 * fact, this method does nothing until it reaches the last item for each
332 * category, at which point it draws all the items for that category).
333 *
334 * @param g2 the graphics device.
335 * @param state the renderer state.
336 * @param dataArea the plot area.
337 * @param plot the plot.
338 * @param domainAxis the domain (category) axis.
339 * @param rangeAxis the range (value) axis.
340 * @param dataset the data.
341 * @param row the row index (zero-based).
342 * @param column the column index (zero-based).
343 * @param pass the pass index.
344 */
345 public void drawItem(Graphics2D g2,
346 CategoryItemRendererState state,
347 Rectangle2D dataArea,
348 CategoryPlot plot,
349 CategoryAxis domainAxis,
350 ValueAxis rangeAxis,
351 CategoryDataset dataset,
352 int row,
353 int column,
354 int pass) {
355
356 // wait till we are at the last item for the row then draw the
357 // whole stack at once
358 if (row < dataset.getRowCount() - 1) {
359 return;
360 }
361 Comparable category = dataset.getColumnKey(column);
362
363 List values = createStackedValueList(dataset,
364 dataset.getColumnKey(column), getBase(),
365 this.renderAsPercentages);
366
367 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
368 dataArea.getY() + getYOffset(),
369 dataArea.getWidth() - getXOffset(),
370 dataArea.getHeight() - getYOffset());
371
372
373 PlotOrientation orientation = plot.getOrientation();
374
375 // handle rendering separately for the two plot orientations...
376 if (orientation == PlotOrientation.HORIZONTAL) {
377 drawStackHorizontal(values, category, g2, state, adjusted, plot,
378 domainAxis, rangeAxis, dataset);
379 }
380 else {
381 drawStackVertical(values, category, g2, state, adjusted, plot,
382 domainAxis, rangeAxis, dataset);
383 }
384
385 }
386
387 /**
388 * Draws a stack of bars for one category, with a horizontal orientation.
389 *
390 * @param values the value list.
391 * @param category the category.
392 * @param g2 the graphics device.
393 * @param state the state.
394 * @param dataArea the data area (adjusted for the 3D effect).
395 * @param plot the plot.
396 * @param domainAxis the domain axis.
397 * @param rangeAxis the range axis.
398 * @param dataset the dataset.
399 *
400 * @since 1.0.4
401 */
402 protected void drawStackHorizontal(List values, Comparable category,
403 Graphics2D g2, CategoryItemRendererState state,
404 Rectangle2D dataArea, CategoryPlot plot,
405 CategoryAxis domainAxis, ValueAxis rangeAxis,
406 CategoryDataset dataset) {
407
408 int column = dataset.getColumnIndex(category);
409 double barX0 = domainAxis.getCategoryMiddle(column,
410 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
411 - state.getBarWidth() / 2.0;
412 double barW = state.getBarWidth();
413
414 // a list to store the series index and bar region, so we can draw
415 // all the labels at the end...
416 List itemLabelList = new ArrayList();
417
418 // draw the blocks
419 boolean inverted = rangeAxis.isInverted();
420 int blockCount = values.size() - 1;
421 for (int k = 0; k < blockCount; k++) {
422 int index = (inverted ? blockCount - k - 1 : k);
423 Object[] prev = (Object[]) values.get(index);
424 Object[] curr = (Object[]) values.get(index + 1);
425 int series = 0;
426 if (curr[0] == null) {
427 series = -((Integer) prev[0]).intValue();
428 }
429 else {
430 series = ((Integer) curr[0]).intValue();
431 if (series < 0) {
432 series = -((Integer) prev[0]).intValue();
433 }
434 }
435 double v0 = ((Double) prev[1]).doubleValue();
436 double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
437 plot.getRangeAxisEdge());
438
439 double v1 = ((Double) curr[1]).doubleValue();
440 double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
441 plot.getRangeAxisEdge());
442
443 Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1,
444 inverted);
445 Paint fillPaint = getItemPaint(series, column);
446 Paint fillPaintDark = fillPaint;
447 if (fillPaintDark instanceof Color) {
448 fillPaintDark = ((Color) fillPaint).darker();
449 }
450 boolean drawOutlines = isDrawBarOutline();
451 Paint outlinePaint = fillPaint;
452 if (drawOutlines) {
453 outlinePaint = getItemOutlinePaint(series, column);
454 g2.setStroke(getItemOutlineStroke(series, column));
455 }
456 for (int f = 0; f < 6; f++) {
457 if (f == 5) {
458 g2.setPaint(fillPaint);
459 }
460 else {
461 g2.setPaint(fillPaintDark);
462 }
463 g2.fill(faces[f]);
464 if (drawOutlines) {
465 g2.setPaint(outlinePaint);
466 g2.draw(faces[f]);
467 }
468 }
469
470 itemLabelList.add(new Object[] {new Integer(series),
471 faces[5].getBounds2D(),
472 BooleanUtilities.valueOf(v0 < getBase())});
473
474 // add an item entity, if this information is being collected
475 EntityCollection entities = state.getEntityCollection();
476 if (entities != null) {
477 addItemEntity(entities, dataset, series, column, faces[5]);
478 }
479
480 }
481
482 for (int i = 0; i < itemLabelList.size(); i++) {
483 Object[] record = (Object[]) itemLabelList.get(i);
484 int series = ((Integer) record[0]).intValue();
485 Rectangle2D bar = (Rectangle2D) record[1];
486 boolean neg = ((Boolean) record[2]).booleanValue();
487 CategoryItemLabelGenerator generator
488 = getItemLabelGenerator(series, column);
489 if (generator != null && isItemLabelVisible(series, column)) {
490 drawItemLabel(g2, dataset, series, column, plot, generator,
491 bar, neg);
492 }
493
494 }
495 }
496
497 /**
498 * Creates an array of shapes representing the six sides of a block in a
499 * horizontal stack.
500 *
501 * @param x0 left edge of bar (in Java2D space).
502 * @param width the width of the bar (in Java2D units).
503 * @param y0 the base of the block (in Java2D space).
504 * @param y1 the top of the block (in Java2D space).
505 * @param inverted a flag indicating whether or not the block is inverted
506 * (this changes the order of the faces of the block).
507 *
508 * @return The sides of the block.
509 */
510 private Shape[] createHorizontalBlock(double x0, double width, double y0,
511 double y1, boolean inverted) {
512 Shape[] result = new Shape[6];
513 Point2D p00 = new Point2D.Double(y0, x0);
514 Point2D p01 = new Point2D.Double(y0, x0 + width);
515 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
516 p01.getY() - getYOffset());
517 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
518 p00.getY() - getYOffset());
519
520 Point2D p0 = new Point2D.Double(y1, x0);
521 Point2D p1 = new Point2D.Double(y1, x0 + width);
522 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
523 p1.getY() - getYOffset());
524 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
525 p0.getY() - getYOffset());
526
527 GeneralPath bottom = new GeneralPath();
528 bottom.moveTo((float) p1.getX(), (float) p1.getY());
529 bottom.lineTo((float) p01.getX(), (float) p01.getY());
530 bottom.lineTo((float) p02.getX(), (float) p02.getY());
531 bottom.lineTo((float) p2.getX(), (float) p2.getY());
532 bottom.closePath();
533
534 GeneralPath top = new GeneralPath();
535 top.moveTo((float) p0.getX(), (float) p0.getY());
536 top.lineTo((float) p00.getX(), (float) p00.getY());
537 top.lineTo((float) p03.getX(), (float) p03.getY());
538 top.lineTo((float) p3.getX(), (float) p3.getY());
539 top.closePath();
540
541 GeneralPath back = new GeneralPath();
542 back.moveTo((float) p2.getX(), (float) p2.getY());
543 back.lineTo((float) p02.getX(), (float) p02.getY());
544 back.lineTo((float) p03.getX(), (float) p03.getY());
545 back.lineTo((float) p3.getX(), (float) p3.getY());
546 back.closePath();
547
548 GeneralPath front = new GeneralPath();
549 front.moveTo((float) p0.getX(), (float) p0.getY());
550 front.lineTo((float) p1.getX(), (float) p1.getY());
551 front.lineTo((float) p01.getX(), (float) p01.getY());
552 front.lineTo((float) p00.getX(), (float) p00.getY());
553 front.closePath();
554
555 GeneralPath left = new GeneralPath();
556 left.moveTo((float) p0.getX(), (float) p0.getY());
557 left.lineTo((float) p1.getX(), (float) p1.getY());
558 left.lineTo((float) p2.getX(), (float) p2.getY());
559 left.lineTo((float) p3.getX(), (float) p3.getY());
560 left.closePath();
561
562 GeneralPath right = new GeneralPath();
563 right.moveTo((float) p00.getX(), (float) p00.getY());
564 right.lineTo((float) p01.getX(), (float) p01.getY());
565 right.lineTo((float) p02.getX(), (float) p02.getY());
566 right.lineTo((float) p03.getX(), (float) p03.getY());
567 right.closePath();
568 result[0] = bottom;
569 result[1] = back;
570 if (inverted) {
571 result[2] = right;
572 result[3] = left;
573 }
574 else {
575 result[2] = left;
576 result[3] = right;
577 }
578 result[4] = top;
579 result[5] = front;
580 return result;
581 }
582
583 /**
584 * Draws a stack of bars for one category, with a vertical orientation.
585 *
586 * @param values the value list.
587 * @param category the category.
588 * @param g2 the graphics device.
589 * @param state the state.
590 * @param dataArea the data area (adjusted for the 3D effect).
591 * @param plot the plot.
592 * @param domainAxis the domain axis.
593 * @param rangeAxis the range axis.
594 * @param dataset the dataset.
595 *
596 * @since 1.0.4
597 */
598 protected void drawStackVertical(List values, Comparable category,
599 Graphics2D g2, CategoryItemRendererState state,
600 Rectangle2D dataArea, CategoryPlot plot,
601 CategoryAxis domainAxis, ValueAxis rangeAxis,
602 CategoryDataset dataset) {
603
604 int column = dataset.getColumnIndex(category);
605 double barX0 = domainAxis.getCategoryMiddle(column,
606 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
607 - state.getBarWidth() / 2.0;
608 double barW = state.getBarWidth();
609
610 // a list to store the series index and bar region, so we can draw
611 // all the labels at the end...
612 List itemLabelList = new ArrayList();
613
614 // draw the blocks
615 boolean inverted = rangeAxis.isInverted();
616 int blockCount = values.size() - 1;
617 for (int k = 0; k < blockCount; k++) {
618 int index = (inverted ? blockCount - k - 1 : k);
619 Object[] prev = (Object[]) values.get(index);
620 Object[] curr = (Object[]) values.get(index + 1);
621 int series = 0;
622 if (curr[0] == null) {
623 series = -((Integer) prev[0]).intValue();
624 }
625 else {
626 series = ((Integer) curr[0]).intValue();
627 if (series < 0) {
628 series = -((Integer) prev[0]).intValue();
629 }
630 }
631 double v0 = ((Double) prev[1]).doubleValue();
632 double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
633 plot.getRangeAxisEdge());
634
635 double v1 = ((Double) curr[1]).doubleValue();
636 double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
637 plot.getRangeAxisEdge());
638
639 Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1,
640 inverted);
641 Paint fillPaint = getItemPaint(series, column);
642 Paint fillPaintDark = fillPaint;
643 if (fillPaintDark instanceof Color) {
644 fillPaintDark = ((Color) fillPaint).darker();
645 }
646 boolean drawOutlines = isDrawBarOutline();
647 Paint outlinePaint = fillPaint;
648 if (drawOutlines) {
649 outlinePaint = getItemOutlinePaint(series, column);
650 g2.setStroke(getItemOutlineStroke(series, column));
651 }
652
653 for (int f = 0; f < 6; f++) {
654 if (f == 5) {
655 g2.setPaint(fillPaint);
656 }
657 else {
658 g2.setPaint(fillPaintDark);
659 }
660 g2.fill(faces[f]);
661 if (drawOutlines) {
662 g2.setPaint(outlinePaint);
663 g2.draw(faces[f]);
664 }
665 }
666
667 itemLabelList.add(new Object[] {new Integer(series),
668 faces[5].getBounds2D(),
669 BooleanUtilities.valueOf(v0 < getBase())});
670
671 // add an item entity, if this information is being collected
672 EntityCollection entities = state.getEntityCollection();
673 if (entities != null) {
674 addItemEntity(entities, dataset, series, column, faces[5]);
675 }
676
677 }
678
679 for (int i = 0; i < itemLabelList.size(); i++) {
680 Object[] record = (Object[]) itemLabelList.get(i);
681 int series = ((Integer) record[0]).intValue();
682 Rectangle2D bar = (Rectangle2D) record[1];
683 boolean neg = ((Boolean) record[2]).booleanValue();
684 CategoryItemLabelGenerator generator
685 = getItemLabelGenerator(series, column);
686 if (generator != null && isItemLabelVisible(series, column)) {
687 drawItemLabel(g2, dataset, series, column, plot, generator,
688 bar, neg);
689 }
690
691 }
692 }
693
694 /**
695 * Creates an array of shapes representing the six sides of a block in a
696 * vertical stack.
697 *
698 * @param x0 left edge of bar (in Java2D space).
699 * @param width the width of the bar (in Java2D units).
700 * @param y0 the base of the block (in Java2D space).
701 * @param y1 the top of the block (in Java2D space).
702 * @param inverted a flag indicating whether or not the block is inverted
703 * (this changes the order of the faces of the block).
704 *
705 * @return The sides of the block.
706 */
707 private Shape[] createVerticalBlock(double x0, double width, double y0,
708 double y1, boolean inverted) {
709 Shape[] result = new Shape[6];
710 Point2D p00 = new Point2D.Double(x0, y0);
711 Point2D p01 = new Point2D.Double(x0 + width, y0);
712 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
713 p01.getY() - getYOffset());
714 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
715 p00.getY() - getYOffset());
716
717
718 Point2D p0 = new Point2D.Double(x0, y1);
719 Point2D p1 = new Point2D.Double(x0 + width, y1);
720 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
721 p1.getY() - getYOffset());
722 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
723 p0.getY() - getYOffset());
724
725 GeneralPath right = new GeneralPath();
726 right.moveTo((float) p1.getX(), (float) p1.getY());
727 right.lineTo((float) p01.getX(), (float) p01.getY());
728 right.lineTo((float) p02.getX(), (float) p02.getY());
729 right.lineTo((float) p2.getX(), (float) p2.getY());
730 right.closePath();
731
732 GeneralPath left = new GeneralPath();
733 left.moveTo((float) p0.getX(), (float) p0.getY());
734 left.lineTo((float) p00.getX(), (float) p00.getY());
735 left.lineTo((float) p03.getX(), (float) p03.getY());
736 left.lineTo((float) p3.getX(), (float) p3.getY());
737 left.closePath();
738
739 GeneralPath back = new GeneralPath();
740 back.moveTo((float) p2.getX(), (float) p2.getY());
741 back.lineTo((float) p02.getX(), (float) p02.getY());
742 back.lineTo((float) p03.getX(), (float) p03.getY());
743 back.lineTo((float) p3.getX(), (float) p3.getY());
744 back.closePath();
745
746 GeneralPath front = new GeneralPath();
747 front.moveTo((float) p0.getX(), (float) p0.getY());
748 front.lineTo((float) p1.getX(), (float) p1.getY());
749 front.lineTo((float) p01.getX(), (float) p01.getY());
750 front.lineTo((float) p00.getX(), (float) p00.getY());
751 front.closePath();
752
753 GeneralPath top = new GeneralPath();
754 top.moveTo((float) p0.getX(), (float) p0.getY());
755 top.lineTo((float) p1.getX(), (float) p1.getY());
756 top.lineTo((float) p2.getX(), (float) p2.getY());
757 top.lineTo((float) p3.getX(), (float) p3.getY());
758 top.closePath();
759
760 GeneralPath bottom = new GeneralPath();
761 bottom.moveTo((float) p00.getX(), (float) p00.getY());
762 bottom.lineTo((float) p01.getX(), (float) p01.getY());
763 bottom.lineTo((float) p02.getX(), (float) p02.getY());
764 bottom.lineTo((float) p03.getX(), (float) p03.getY());
765 bottom.closePath();
766
767 result[0] = bottom;
768 result[1] = back;
769 result[2] = left;
770 result[3] = right;
771 result[4] = top;
772 result[5] = front;
773 if (inverted) {
774 result[0] = top;
775 result[4] = bottom;
776 }
777 return result;
778 }
779
780 /**
781 * Tests this renderer for equality with an arbitrary object.
782 *
783 * @param obj the object (<code>null</code> permitted).
784 *
785 * @return A boolean.
786 */
787 public boolean equals(Object obj) {
788 if (obj == this) {
789 return true;
790 }
791 if (!(obj instanceof StackedBarRenderer3D)) {
792 return false;
793 }
794 if (!super.equals(obj)) {
795 return false;
796 }
797 StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
798 if (this.renderAsPercentages != that.getRenderAsPercentages()) {
799 return false;
800 }
801 return true;
802 }
803
804 }