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: * StackedBarRenderer.java
029: * -----------------------
030: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): Richard Atkinson;
034: * Thierry Saura;
035: * Christian W. Zuckschwerdt;
036: *
037: * $Id: StackedBarRenderer.java,v 1.10.2.9 2007/03/15 16:55:06 mungady Exp $
038: *
039: * Changes
040: * -------
041: * 19-Oct-2001 : Version 1 (DG);
042: * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
043: * 23-Oct-2001 : Changed intro and trail gaps on bar plots to use percentage of
044: * available space rather than a fixed number of units (DG);
045: * 15-Nov-2001 : Modified to allow for null data values (DG);
046: * 22-Nov-2001 : Modified to allow for negative data values (DG);
047: * 13-Dec-2001 : Added tooltips (DG);
048: * 16-Jan-2002 : Fixed bug for single category datasets (DG);
049: * 15-Feb-2002 : Added isStacked() method (DG);
050: * 14-Mar-2002 : Modified to implement the CategoryItemRenderer interface (DG);
051: * 24-May-2002 : Incorporated tooltips into chart entities (DG);
052: * 11-Jun-2002 : Added check for (permitted) null info object, bug and fix
053: * reported by David Basten. Also updated Javadocs. (DG);
054: * 25-Jun-2002 : Removed redundant import (DG);
055: * 26-Jun-2002 : Small change to entity (DG);
056: * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
057: * for HTML image maps (RA);
058: * 08-Aug-2002 : Added optional linking lines, contributed by Thierry
059: * Saura (DG);
060: * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
061: * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
062: * CategoryToolTipGenerator interface (DG);
063: * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
064: * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
065: * 17-Jan-2003 : Moved plot classes to a separate package (DG);
066: * 25-Mar-2003 : Implemented Serializable (DG);
067: * 12-May-2003 : Merged horizontal and vertical stacked bar renderers (DG);
068: * 30-Jul-2003 : Modified entity constructor (CZ);
069: * 08-Sep-2003 : Fixed bug 799668 (isBarOutlineDrawn() ignored) (DG);
070: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
071: * 21-Oct-2003 : Moved bar width into renderer state (DG);
072: * 26-Nov-2003 : Added code to respect maxBarWidth attribute (DG);
073: * 05-Nov-2004 : Changed to a two-pass renderer so that item labels are not
074: * overwritten by other bars (DG);
075: * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() (DG);
076: * 29-Mar-2005 : Modified drawItem() method so that a zero value is handled
077: * within the code for positive rather than negative values (DG);
078: * 20-Apr-2005 : Renamed CategoryLabelGenerator
079: * --> CategoryItemLabelGenerator (DG);
080: * 17-May-2005 : Added flag to allow rendering values as percentages - inspired
081: * by patch 1200886 submitted by John Xiao (DG);
082: * 09-Jun-2005 : Added accessor methods for the renderAsPercentages flag,
083: * provided equals() method, and use addItemEntity from
084: * superclass (DG);
085: * 09-Jun-2005 : Added support for GradientPaint - see bug report 1215670 (DG);
086: * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
087: * 29-Sep-2005 : Use outline stroke in drawItem method - see bug report
088: * 1304139 (DG);
089: * ------------- JFREECHART 1.0.x ---------------------------------------------
090: * 11-Oct-2006 : Source reformatting (DG);
091: *
092: */
093:
094: package org.jfree.chart.renderer.category;
095:
096: import java.awt.GradientPaint;
097: import java.awt.Graphics2D;
098: import java.awt.Paint;
099: import java.awt.geom.Rectangle2D;
100: import java.io.Serializable;
101:
102: import org.jfree.chart.axis.CategoryAxis;
103: import org.jfree.chart.axis.ValueAxis;
104: import org.jfree.chart.entity.EntityCollection;
105: import org.jfree.chart.event.RendererChangeEvent;
106: import org.jfree.chart.labels.CategoryItemLabelGenerator;
107: import org.jfree.chart.labels.ItemLabelAnchor;
108: import org.jfree.chart.labels.ItemLabelPosition;
109: import org.jfree.chart.plot.CategoryPlot;
110: import org.jfree.chart.plot.PlotOrientation;
111: import org.jfree.data.DataUtilities;
112: import org.jfree.data.Range;
113: import org.jfree.data.category.CategoryDataset;
114: import org.jfree.data.general.DatasetUtilities;
115: import org.jfree.ui.GradientPaintTransformer;
116: import org.jfree.ui.RectangleEdge;
117: import org.jfree.ui.TextAnchor;
118: import org.jfree.util.PublicCloneable;
119:
120: /**
121: * A stacked bar renderer for use with the
122: * {@link org.jfree.chart.plot.CategoryPlot} class.
123: */
124: public class StackedBarRenderer extends BarRenderer implements
125: Cloneable, PublicCloneable, Serializable {
126:
127: /** For serialization. */
128: static final long serialVersionUID = 6402943811500067531L;
129:
130: /** A flag that controls whether the bars display values or percentages. */
131: private boolean renderAsPercentages;
132:
133: /**
134: * Creates a new renderer. By default, the renderer has no tool tip
135: * generator and no URL generator. These defaults have been chosen to
136: * minimise the processing required to generate a default chart. If you
137: * require tool tips or URLs, then you can easily add the required
138: * generators.
139: */
140: public StackedBarRenderer() {
141: this (false);
142: }
143:
144: /**
145: * Creates a new renderer.
146: *
147: * @param renderAsPercentages a flag that controls whether the data values
148: * are rendered as percentages.
149: */
150: public StackedBarRenderer(boolean renderAsPercentages) {
151: super ();
152: this .renderAsPercentages = renderAsPercentages;
153:
154: // set the default item label positions, which will only be used if
155: // the user requests visible item labels...
156: ItemLabelPosition p = new ItemLabelPosition(
157: ItemLabelAnchor.CENTER, TextAnchor.CENTER);
158: setBasePositiveItemLabelPosition(p);
159: setBaseNegativeItemLabelPosition(p);
160: setPositiveItemLabelPositionFallback(null);
161: setNegativeItemLabelPositionFallback(null);
162: }
163:
164: /**
165: * Returns <code>true</code> if the renderer displays each item value as
166: * a percentage (so that the stacked bars add to 100%), and
167: * <code>false</code> otherwise.
168: *
169: * @return A boolean.
170: *
171: * @see #setRenderAsPercentages(boolean)
172: */
173: public boolean getRenderAsPercentages() {
174: return this .renderAsPercentages;
175: }
176:
177: /**
178: * Sets the flag that controls whether the renderer displays each item
179: * value as a percentage (so that the stacked bars add to 100%), and sends
180: * a {@link RendererChangeEvent} to all registered listeners.
181: *
182: * @param asPercentages the flag.
183: *
184: * @see #getRenderAsPercentages()
185: */
186: public void setRenderAsPercentages(boolean asPercentages) {
187: this .renderAsPercentages = asPercentages;
188: notifyListeners(new RendererChangeEvent(this ));
189: }
190:
191: /**
192: * Returns the number of passes (<code>2</code>) required by this renderer.
193: * The first pass is used to draw the bars, the second pass is used to
194: * draw the item labels (if visible).
195: *
196: * @return The number of passes required by the renderer.
197: */
198: public int getPassCount() {
199: return 2;
200: }
201:
202: /**
203: * Returns the range of values the renderer requires to display all the
204: * items from the specified dataset.
205: *
206: * @param dataset the dataset (<code>null</code> permitted).
207: *
208: * @return The range (or <code>null</code> if the dataset is empty).
209: */
210: public Range findRangeBounds(CategoryDataset dataset) {
211: if (this .renderAsPercentages) {
212: return new Range(0.0, 1.0);
213: } else {
214: return DatasetUtilities.findStackedRangeBounds(dataset,
215: getBase());
216: }
217: }
218:
219: /**
220: * Calculates the bar width and stores it in the renderer state.
221: *
222: * @param plot the plot.
223: * @param dataArea the data area.
224: * @param rendererIndex the renderer index.
225: * @param state the renderer state.
226: */
227: protected void calculateBarWidth(CategoryPlot plot,
228: Rectangle2D dataArea, int rendererIndex,
229: CategoryItemRendererState state) {
230:
231: // calculate the bar width
232: CategoryAxis xAxis = plot
233: .getDomainAxisForDataset(rendererIndex);
234: CategoryDataset data = plot.getDataset(rendererIndex);
235: if (data != null) {
236: PlotOrientation orientation = plot.getOrientation();
237: double space = 0.0;
238: if (orientation == PlotOrientation.HORIZONTAL) {
239: space = dataArea.getHeight();
240: } else if (orientation == PlotOrientation.VERTICAL) {
241: space = dataArea.getWidth();
242: }
243: double maxWidth = space * getMaximumBarWidth();
244: int columns = data.getColumnCount();
245: double categoryMargin = 0.0;
246: if (columns > 1) {
247: categoryMargin = xAxis.getCategoryMargin();
248: }
249:
250: double used = space
251: * (1 - xAxis.getLowerMargin()
252: - xAxis.getUpperMargin() - categoryMargin);
253: if (columns > 0) {
254: state.setBarWidth(Math.min(used / columns, maxWidth));
255: } else {
256: state.setBarWidth(Math.min(used, maxWidth));
257: }
258: }
259:
260: }
261:
262: /**
263: * Draws a stacked bar for a specific item.
264: *
265: * @param g2 the graphics device.
266: * @param state the renderer state.
267: * @param dataArea the plot area.
268: * @param plot the plot.
269: * @param domainAxis the domain (category) axis.
270: * @param rangeAxis the range (value) axis.
271: * @param dataset the data.
272: * @param row the row index (zero-based).
273: * @param column the column index (zero-based).
274: * @param pass the pass index.
275: */
276: public void drawItem(Graphics2D g2,
277: CategoryItemRendererState state, Rectangle2D dataArea,
278: CategoryPlot plot, CategoryAxis domainAxis,
279: ValueAxis rangeAxis, CategoryDataset dataset, int row,
280: int column, int pass) {
281:
282: // nothing is drawn for null values...
283: Number dataValue = dataset.getValue(row, column);
284: if (dataValue == null) {
285: return;
286: }
287:
288: double value = dataValue.doubleValue();
289: double total = 0.0; // only needed if calculating percentages
290: if (this .renderAsPercentages) {
291: total = DataUtilities.calculateColumnTotal(dataset, column);
292: value = value / total;
293: }
294:
295: PlotOrientation orientation = plot.getOrientation();
296: double barW0 = domainAxis.getCategoryMiddle(column,
297: getColumnCount(), dataArea, plot.getDomainAxisEdge())
298: - state.getBarWidth() / 2.0;
299:
300: double positiveBase = getBase();
301: double negativeBase = positiveBase;
302:
303: for (int i = 0; i < row; i++) {
304: Number v = dataset.getValue(i, column);
305: if (v != null) {
306: double d = v.doubleValue();
307: if (this .renderAsPercentages) {
308: d = d / total;
309: }
310: if (d > 0) {
311: positiveBase = positiveBase + d;
312: } else {
313: negativeBase = negativeBase + d;
314: }
315: }
316: }
317:
318: double translatedBase;
319: double translatedValue;
320: RectangleEdge location = plot.getRangeAxisEdge();
321: if (value >= 0.0) {
322: translatedBase = rangeAxis.valueToJava2D(positiveBase,
323: dataArea, location);
324: translatedValue = rangeAxis.valueToJava2D(positiveBase
325: + value, dataArea, location);
326: } else {
327: translatedBase = rangeAxis.valueToJava2D(negativeBase,
328: dataArea, location);
329: translatedValue = rangeAxis.valueToJava2D(negativeBase
330: + value, dataArea, location);
331: }
332: double barL0 = Math.min(translatedBase, translatedValue);
333: double barLength = Math.max(Math.abs(translatedValue
334: - translatedBase), getMinimumBarLength());
335:
336: Rectangle2D bar = null;
337: if (orientation == PlotOrientation.HORIZONTAL) {
338: bar = new Rectangle2D.Double(barL0, barW0, barLength, state
339: .getBarWidth());
340: } else {
341: bar = new Rectangle2D.Double(barW0, barL0, state
342: .getBarWidth(), barLength);
343: }
344: if (pass == 0) {
345: Paint itemPaint = getItemPaint(row, column);
346: GradientPaintTransformer t = getGradientPaintTransformer();
347: if (t != null && itemPaint instanceof GradientPaint) {
348: itemPaint = t.transform((GradientPaint) itemPaint, bar);
349: }
350: g2.setPaint(itemPaint);
351: g2.fill(bar);
352: if (isDrawBarOutline()
353: && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
354: g2.setStroke(getItemOutlineStroke(row, column));
355: g2.setPaint(getItemOutlinePaint(row, column));
356: g2.draw(bar);
357: }
358:
359: // add an item entity, if this information is being collected
360: EntityCollection entities = state.getEntityCollection();
361: if (entities != null) {
362: addItemEntity(entities, dataset, row, column, bar);
363: }
364: } else if (pass == 1) {
365: CategoryItemLabelGenerator generator = getItemLabelGenerator(
366: row, column);
367: if (generator != null && isItemLabelVisible(row, column)) {
368: drawItemLabel(g2, dataset, row, column, plot,
369: generator, bar, (value < 0.0));
370: }
371: }
372: }
373:
374: /**
375: * Tests this renderer for equality with an arbitrary object.
376: *
377: * @param obj the object (<code>null</code> permitted).
378: *
379: * @return A boolean.
380: */
381: public boolean equals(Object obj) {
382: if (obj == this ) {
383: return true;
384: }
385: if (!(obj instanceof StackedBarRenderer)) {
386: return false;
387: }
388: StackedBarRenderer that = (StackedBarRenderer) obj;
389: if (this .renderAsPercentages != that.renderAsPercentages) {
390: return false;
391: }
392: return super.equals(obj);
393: }
394:
395: }
|