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: * GroupedStackedBarRenderer.java
029: * ------------------------------
030: * (C) Copyright 2004-2007, by Object Refinery Limited and Contributors.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: GroupedStackedBarRenderer.java,v 1.7.2.4 2007/06/01 15:12:15 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 29-Apr-2004 : Version 1 (DG);
040: * 08-Jul-2004 : Added equals() method (DG);
041: * 05-Nov-2004 : Modified drawItem() signature (DG);
042: * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
043: * 20-Apr-2005 : Renamed CategoryLabelGenerator
044: * --> CategoryItemLabelGenerator (DG);
045: * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
046: *
047: */
048:
049: package org.jfree.chart.renderer.category;
050:
051: import java.awt.GradientPaint;
052: import java.awt.Graphics2D;
053: import java.awt.Paint;
054: import java.awt.geom.Rectangle2D;
055: import java.io.Serializable;
056:
057: import org.jfree.chart.axis.CategoryAxis;
058: import org.jfree.chart.axis.ValueAxis;
059: import org.jfree.chart.entity.CategoryItemEntity;
060: import org.jfree.chart.entity.EntityCollection;
061: import org.jfree.chart.event.RendererChangeEvent;
062: import org.jfree.chart.labels.CategoryItemLabelGenerator;
063: import org.jfree.chart.labels.CategoryToolTipGenerator;
064: import org.jfree.chart.plot.CategoryPlot;
065: import org.jfree.chart.plot.PlotOrientation;
066: import org.jfree.data.KeyToGroupMap;
067: import org.jfree.data.Range;
068: import org.jfree.data.category.CategoryDataset;
069: import org.jfree.data.general.DatasetUtilities;
070: import org.jfree.ui.RectangleEdge;
071: import org.jfree.util.PublicCloneable;
072:
073: /**
074: * A renderer that draws stacked bars within groups. This will probably be
075: * merged with the {@link StackedBarRenderer} class at some point.
076: */
077: public class GroupedStackedBarRenderer extends StackedBarRenderer
078: implements Cloneable, PublicCloneable, Serializable {
079:
080: /** For serialization. */
081: private static final long serialVersionUID = -2725921399005922939L;
082:
083: /** A map used to assign each series to a group. */
084: private KeyToGroupMap seriesToGroupMap;
085:
086: /**
087: * Creates a new renderer.
088: */
089: public GroupedStackedBarRenderer() {
090: super ();
091: this .seriesToGroupMap = new KeyToGroupMap();
092: }
093:
094: /**
095: * Updates the map used to assign each series to a group.
096: *
097: * @param map the map (<code>null</code> not permitted).
098: */
099: public void setSeriesToGroupMap(KeyToGroupMap map) {
100: if (map == null) {
101: throw new IllegalArgumentException("Null 'map' argument.");
102: }
103: this .seriesToGroupMap = map;
104: notifyListeners(new RendererChangeEvent(this ));
105: }
106:
107: /**
108: * Returns the range of values the renderer requires to display all the
109: * items from the specified dataset.
110: *
111: * @param dataset the dataset (<code>null</code> permitted).
112: *
113: * @return The range (or <code>null</code> if the dataset is
114: * <code>null</code> or empty).
115: */
116: public Range findRangeBounds(CategoryDataset dataset) {
117: Range r = DatasetUtilities.findStackedRangeBounds(dataset,
118: this .seriesToGroupMap);
119: return r;
120: }
121:
122: /**
123: * Calculates the bar width and stores it in the renderer state. We
124: * override the method in the base class to take account of the
125: * series-to-group mapping.
126: *
127: * @param plot the plot.
128: * @param dataArea the data area.
129: * @param rendererIndex the renderer index.
130: * @param state the renderer state.
131: */
132: protected void calculateBarWidth(CategoryPlot plot,
133: Rectangle2D dataArea, int rendererIndex,
134: CategoryItemRendererState state) {
135:
136: // calculate the bar width
137: CategoryAxis xAxis = plot
138: .getDomainAxisForDataset(rendererIndex);
139: CategoryDataset data = plot.getDataset(rendererIndex);
140: if (data != null) {
141: PlotOrientation orientation = plot.getOrientation();
142: double space = 0.0;
143: if (orientation == PlotOrientation.HORIZONTAL) {
144: space = dataArea.getHeight();
145: } else if (orientation == PlotOrientation.VERTICAL) {
146: space = dataArea.getWidth();
147: }
148: double maxWidth = space * getMaximumBarWidth();
149: int groups = this .seriesToGroupMap.getGroupCount();
150: int categories = data.getColumnCount();
151: int columns = groups * categories;
152: double categoryMargin = 0.0;
153: double itemMargin = 0.0;
154: if (categories > 1) {
155: categoryMargin = xAxis.getCategoryMargin();
156: }
157: if (groups > 1) {
158: itemMargin = getItemMargin();
159: }
160:
161: double used = space
162: * (1 - xAxis.getLowerMargin()
163: - xAxis.getUpperMargin() - categoryMargin - itemMargin);
164: if (columns > 0) {
165: state.setBarWidth(Math.min(used / columns, maxWidth));
166: } else {
167: state.setBarWidth(Math.min(used, maxWidth));
168: }
169: }
170:
171: }
172:
173: /**
174: * Calculates the coordinate of the first "side" of a bar. This will be
175: * the minimum x-coordinate for a vertical bar, and the minimum
176: * y-coordinate for a horizontal bar.
177: *
178: * @param plot the plot.
179: * @param orientation the plot orientation.
180: * @param dataArea the data area.
181: * @param domainAxis the domain axis.
182: * @param state the renderer state (has the bar width precalculated).
183: * @param row the row index.
184: * @param column the column index.
185: *
186: * @return The coordinate.
187: */
188: protected double calculateBarW0(CategoryPlot plot,
189: PlotOrientation orientation, Rectangle2D dataArea,
190: CategoryAxis domainAxis, CategoryItemRendererState state,
191: int row, int column) {
192: // calculate bar width...
193: double space = 0.0;
194: if (orientation == PlotOrientation.HORIZONTAL) {
195: space = dataArea.getHeight();
196: } else {
197: space = dataArea.getWidth();
198: }
199: double barW0 = domainAxis.getCategoryStart(column,
200: getColumnCount(), dataArea, plot.getDomainAxisEdge());
201: int groupCount = this .seriesToGroupMap.getGroupCount();
202: int groupIndex = this .seriesToGroupMap
203: .getGroupIndex(this .seriesToGroupMap.getGroup(plot
204: .getDataset().getRowKey(row)));
205: int categoryCount = getColumnCount();
206: if (groupCount > 1) {
207: double groupGap = space * getItemMargin()
208: / (categoryCount * (groupCount - 1));
209: double groupW = calculateSeriesWidth(space, domainAxis,
210: categoryCount, groupCount);
211: barW0 = barW0 + groupIndex * (groupW + groupGap)
212: + (groupW / 2.0) - (state.getBarWidth() / 2.0);
213: } else {
214: barW0 = domainAxis.getCategoryMiddle(column,
215: getColumnCount(), dataArea, plot
216: .getDomainAxisEdge())
217: - state.getBarWidth() / 2.0;
218: }
219: return barW0;
220: }
221:
222: /**
223: * Draws a stacked bar for a specific item.
224: *
225: * @param g2 the graphics device.
226: * @param state the renderer state.
227: * @param dataArea the plot area.
228: * @param plot the plot.
229: * @param domainAxis the domain (category) axis.
230: * @param rangeAxis the range (value) axis.
231: * @param dataset the data.
232: * @param row the row index (zero-based).
233: * @param column the column index (zero-based).
234: * @param pass the pass index.
235: */
236: public void drawItem(Graphics2D g2,
237: CategoryItemRendererState state, Rectangle2D dataArea,
238: CategoryPlot plot, CategoryAxis domainAxis,
239: ValueAxis rangeAxis, CategoryDataset dataset, int row,
240: int column, int pass) {
241:
242: // nothing is drawn for null values...
243: Number dataValue = dataset.getValue(row, column);
244: if (dataValue == null) {
245: return;
246: }
247:
248: double value = dataValue.doubleValue();
249: Comparable group = this .seriesToGroupMap.getGroup(dataset
250: .getRowKey(row));
251: PlotOrientation orientation = plot.getOrientation();
252: double barW0 = calculateBarW0(plot, orientation, dataArea,
253: domainAxis, state, row, column);
254:
255: double positiveBase = 0.0;
256: double negativeBase = 0.0;
257:
258: for (int i = 0; i < row; i++) {
259: if (group.equals(this .seriesToGroupMap.getGroup(dataset
260: .getRowKey(i)))) {
261: Number v = dataset.getValue(i, column);
262: if (v != null) {
263: double d = v.doubleValue();
264: if (d > 0) {
265: positiveBase = positiveBase + d;
266: } else {
267: negativeBase = negativeBase + d;
268: }
269: }
270: }
271: }
272:
273: double translatedBase;
274: double translatedValue;
275: RectangleEdge location = plot.getRangeAxisEdge();
276: if (value > 0.0) {
277: translatedBase = rangeAxis.valueToJava2D(positiveBase,
278: dataArea, location);
279: translatedValue = rangeAxis.valueToJava2D(positiveBase
280: + value, dataArea, location);
281: } else {
282: translatedBase = rangeAxis.valueToJava2D(negativeBase,
283: dataArea, location);
284: translatedValue = rangeAxis.valueToJava2D(negativeBase
285: + value, dataArea, location);
286: }
287: double barL0 = Math.min(translatedBase, translatedValue);
288: double barLength = Math.max(Math.abs(translatedValue
289: - translatedBase), getMinimumBarLength());
290:
291: Rectangle2D bar = null;
292: if (orientation == PlotOrientation.HORIZONTAL) {
293: bar = new Rectangle2D.Double(barL0, barW0, barLength, state
294: .getBarWidth());
295: } else {
296: bar = new Rectangle2D.Double(barW0, barL0, state
297: .getBarWidth(), barLength);
298: }
299: Paint itemPaint = getItemPaint(row, column);
300: if (getGradientPaintTransformer() != null
301: && itemPaint instanceof GradientPaint) {
302: GradientPaint gp = (GradientPaint) itemPaint;
303: itemPaint = getGradientPaintTransformer()
304: .transform(gp, bar);
305: }
306: g2.setPaint(itemPaint);
307: g2.fill(bar);
308: if (isDrawBarOutline()
309: && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
310: g2.setStroke(getItemStroke(row, column));
311: g2.setPaint(getItemOutlinePaint(row, column));
312: g2.draw(bar);
313: }
314:
315: CategoryItemLabelGenerator generator = getItemLabelGenerator(
316: row, column);
317: if (generator != null && isItemLabelVisible(row, column)) {
318: drawItemLabel(g2, dataset, row, column, plot, generator,
319: bar, (value < 0.0));
320: }
321:
322: // collect entity and tool tip information...
323: if (state.getInfo() != null) {
324: EntityCollection entities = state.getEntityCollection();
325: if (entities != null) {
326: String tip = null;
327: CategoryToolTipGenerator tipster = getToolTipGenerator(
328: row, column);
329: if (tipster != null) {
330: tip = tipster.generateToolTip(dataset, row, column);
331: }
332: String url = null;
333: if (getItemURLGenerator(row, column) != null) {
334: url = getItemURLGenerator(row, column).generateURL(
335: dataset, row, column);
336: }
337: CategoryItemEntity entity = new CategoryItemEntity(bar,
338: tip, url, dataset, dataset.getRowKey(row),
339: dataset.getColumnKey(column));
340: entities.add(entity);
341: }
342: }
343:
344: }
345:
346: /**
347: * Tests this renderer for equality with an arbitrary object.
348: *
349: * @param obj the object (<code>null</code> permitted).
350: *
351: * @return A boolean.
352: */
353: public boolean equals(Object obj) {
354: if (obj == this ) {
355: return true;
356: }
357: if (obj instanceof GroupedStackedBarRenderer
358: && super .equals(obj)) {
359: GroupedStackedBarRenderer r = (GroupedStackedBarRenderer) obj;
360: if (!r.seriesToGroupMap.equals(this .seriesToGroupMap)) {
361: return false;
362: }
363: return true;
364: }
365: return false;
366: }
367:
368: }
|