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