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