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: * LevelRenderer.java
029: * ------------------
030: * (C) Copyright 2004-2007, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: LevelRenderer.java,v 1.7.2.4 2007/06/01 15:12:15 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 09-Jan-2004 : Version 1 (DG);
040: * 05-Nov-2004 : Modified drawItem() signature (DG);
041: * 20-Apr-2005 : Renamed CategoryLabelGenerator
042: * --> CategoryItemLabelGenerator (DG);
043: * ------------- JFREECHART 1.0.0 ---------------------------------------------
044: * 23-Jan-2006 : Renamed getMaxItemWidth() --> getMaximumItemWidth() (DG);
045: *
046: */
047:
048: package org.jfree.chart.renderer.category;
049:
050: import java.awt.Graphics2D;
051: import java.awt.Paint;
052: import java.awt.Stroke;
053: import java.awt.geom.Line2D;
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.chart.plot.PlotRenderingInfo;
067: import org.jfree.data.category.CategoryDataset;
068: import org.jfree.ui.RectangleEdge;
069: import org.jfree.util.PublicCloneable;
070:
071: /**
072: * A {@link CategoryItemRenderer} that draws individual data items as
073: * horizontal lines, spaced in the same way as bars in a bar chart.
074: */
075: public class LevelRenderer extends AbstractCategoryItemRenderer
076: implements Cloneable, PublicCloneable, Serializable {
077:
078: /** For serialization. */
079: private static final long serialVersionUID = -8204856624355025117L;
080:
081: /** The default item margin percentage. */
082: public static final double DEFAULT_ITEM_MARGIN = 0.20;
083:
084: /** The margin between items within a category. */
085: private double itemMargin;
086:
087: /** The maximum item width as a percentage of the available space. */
088: private double maxItemWidth;
089:
090: /**
091: * Creates a new renderer with default settings.
092: */
093: public LevelRenderer() {
094: super ();
095: this .itemMargin = DEFAULT_ITEM_MARGIN;
096: this .maxItemWidth = 1.0; // 100 percent, so it will not apply unless
097: // changed
098: }
099:
100: /**
101: * Returns the item margin.
102: *
103: * @return The margin.
104: */
105: public double getItemMargin() {
106: return this .itemMargin;
107: }
108:
109: /**
110: * Sets the item margin. The value is expressed as a percentage of the
111: * available width for plotting all the bars, with the resulting amount to
112: * be distributed between all the bars evenly.
113: *
114: * @param percent the new margin.
115: */
116: public void setItemMargin(double percent) {
117: this .itemMargin = percent;
118: notifyListeners(new RendererChangeEvent(this ));
119: }
120:
121: /**
122: * Returns the maximum width, as a percentage of the available drawing
123: * space.
124: *
125: * @return The maximum width.
126: *
127: * @deprecated Use {@link #getMaximumItemWidth()} instead.
128: */
129: public double getMaxItemWidth() {
130: return this .maxItemWidth;
131: }
132:
133: /**
134: * Sets the maximum item width, which is specified as a percentage of the
135: * available space for all items, and sends a {@link RendererChangeEvent}
136: * to all registered listeners.
137: *
138: * @param percent the percent.
139: *
140: * @deprecated Use {@link #setMaximumItemWidth(double)} instead.
141: */
142: public void setMaxItemWidth(double percent) {
143: this .maxItemWidth = percent;
144: notifyListeners(new RendererChangeEvent(this ));
145: }
146:
147: /**
148: * Returns the maximum width, as a percentage of the available drawing
149: * space.
150: *
151: * @return The maximum width.
152: */
153: public double getMaximumItemWidth() {
154: return getMaxItemWidth();
155: }
156:
157: /**
158: * Sets the maximum item width, which is specified as a percentage of the
159: * available space for all items, and sends a {@link RendererChangeEvent}
160: * to all registered listeners.
161: *
162: * @param percent the percent.
163: */
164: public void setMaximumItemWidth(double percent) {
165: setMaxItemWidth(percent);
166: }
167:
168: /**
169: * Initialises the renderer and returns a state object that will be passed
170: * to subsequent calls to the drawItem method.
171: * <p>
172: * This method gets called once at the start of the process of drawing a
173: * chart.
174: *
175: * @param g2 the graphics device.
176: * @param dataArea the area in which the data is to be plotted.
177: * @param plot the plot.
178: * @param rendererIndex the renderer index.
179: * @param info collects chart rendering information for return to caller.
180: *
181: * @return The renderer state.
182: *
183: */
184: public CategoryItemRendererState initialise(Graphics2D g2,
185: Rectangle2D dataArea, CategoryPlot plot, int rendererIndex,
186: PlotRenderingInfo info) {
187:
188: CategoryItemRendererState state = super .initialise(g2,
189: dataArea, plot, rendererIndex, info);
190: calculateItemWidth(plot, dataArea, rendererIndex, state);
191: return state;
192:
193: }
194:
195: /**
196: * Calculates the bar width and stores it in the renderer state.
197: *
198: * @param plot the plot.
199: * @param dataArea the data area.
200: * @param rendererIndex the renderer index.
201: * @param state the renderer state.
202: */
203: protected void calculateItemWidth(CategoryPlot plot,
204: Rectangle2D dataArea, int rendererIndex,
205: CategoryItemRendererState state) {
206:
207: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
208: CategoryDataset dataset = plot.getDataset(rendererIndex);
209: if (dataset != null) {
210: int columns = dataset.getColumnCount();
211: int rows = dataset.getRowCount();
212: double space = 0.0;
213: PlotOrientation orientation = plot.getOrientation();
214: if (orientation == PlotOrientation.HORIZONTAL) {
215: space = dataArea.getHeight();
216: } else if (orientation == PlotOrientation.VERTICAL) {
217: space = dataArea.getWidth();
218: }
219: double maxWidth = space * getMaxItemWidth();
220: double categoryMargin = 0.0;
221: double currentItemMargin = 0.0;
222: if (columns > 1) {
223: categoryMargin = domainAxis.getCategoryMargin();
224: }
225: if (rows > 1) {
226: currentItemMargin = getItemMargin();
227: }
228: double used = space
229: * (1 - domainAxis.getLowerMargin()
230: - domainAxis.getUpperMargin()
231: - categoryMargin - currentItemMargin);
232: if ((rows * columns) > 0) {
233: state.setBarWidth(Math.min(used / (rows * columns),
234: maxWidth));
235: } else {
236: state.setBarWidth(Math.min(used, maxWidth));
237: }
238: }
239: }
240:
241: /**
242: * Calculates the coordinate of the first "side" of a bar. This will be
243: * the minimum x-coordinate for a vertical bar, and the minimum
244: * y-coordinate for a horizontal bar.
245: *
246: * @param plot the plot.
247: * @param orientation the plot orientation.
248: * @param dataArea the data area.
249: * @param domainAxis the domain axis.
250: * @param state the renderer state (has the bar width precalculated).
251: * @param row the row index.
252: * @param column the column index.
253: *
254: * @return The coordinate.
255: */
256: protected double calculateBarW0(CategoryPlot plot,
257: PlotOrientation orientation, Rectangle2D dataArea,
258: CategoryAxis domainAxis, CategoryItemRendererState state,
259: int row, int column) {
260: // calculate bar width...
261: double space = 0.0;
262: if (orientation == PlotOrientation.HORIZONTAL) {
263: space = dataArea.getHeight();
264: } else {
265: space = dataArea.getWidth();
266: }
267: double barW0 = domainAxis.getCategoryStart(column,
268: getColumnCount(), dataArea, plot.getDomainAxisEdge());
269: int seriesCount = getRowCount();
270: int categoryCount = getColumnCount();
271: if (seriesCount > 1) {
272: double seriesGap = space * getItemMargin()
273: / (categoryCount * (seriesCount - 1));
274: double seriesW = calculateSeriesWidth(space, domainAxis,
275: categoryCount, seriesCount);
276: barW0 = barW0 + row * (seriesW + seriesGap)
277: + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
278: } else {
279: barW0 = domainAxis.getCategoryMiddle(column,
280: getColumnCount(), dataArea, plot
281: .getDomainAxisEdge())
282: - state.getBarWidth() / 2.0;
283: }
284: return barW0;
285: }
286:
287: /**
288: * Draws the bar for a single (series, category) data item.
289: *
290: * @param g2 the graphics device.
291: * @param state the renderer state.
292: * @param dataArea the data area.
293: * @param plot the plot.
294: * @param domainAxis the domain axis.
295: * @param rangeAxis the range axis.
296: * @param dataset the dataset.
297: * @param row the row index (zero-based).
298: * @param column the column index (zero-based).
299: * @param pass the pass index.
300: */
301: public void drawItem(Graphics2D g2,
302: CategoryItemRendererState state, Rectangle2D dataArea,
303: CategoryPlot plot, CategoryAxis domainAxis,
304: ValueAxis rangeAxis, CategoryDataset dataset, int row,
305: int column, int pass) {
306:
307: // nothing is drawn for null values...
308: Number dataValue = dataset.getValue(row, column);
309: if (dataValue == null) {
310: return;
311: }
312:
313: double value = dataValue.doubleValue();
314:
315: PlotOrientation orientation = plot.getOrientation();
316: double barW0 = calculateBarW0(plot, orientation, dataArea,
317: domainAxis, state, row, column);
318: RectangleEdge edge = plot.getRangeAxisEdge();
319: double barL = rangeAxis.valueToJava2D(value, dataArea, edge);
320:
321: // draw the bar...
322: Line2D line = null;
323: double x = 0.0;
324: double y = 0.0;
325: if (orientation == PlotOrientation.HORIZONTAL) {
326: x = barL;
327: y = barW0 + state.getBarWidth() / 2.0;
328: line = new Line2D.Double(barL, barW0, barL, barW0
329: + state.getBarWidth());
330: } else {
331: x = barW0 + state.getBarWidth() / 2.0;
332: y = barL;
333: line = new Line2D.Double(barW0, barL, barW0
334: + state.getBarWidth(), barL);
335: }
336: Stroke itemStroke = getItemStroke(row, column);
337: Paint itemPaint = getItemPaint(row, column);
338: g2.setStroke(itemStroke);
339: g2.setPaint(itemPaint);
340: g2.draw(line);
341:
342: CategoryItemLabelGenerator generator = getItemLabelGenerator(
343: row, column);
344: if (generator != null && isItemLabelVisible(row, column)) {
345: drawItemLabel(g2, orientation, dataset, row, column, x, y,
346: (value < 0.0));
347: }
348:
349: // collect entity and tool tip information...
350: if (state.getInfo() != null) {
351: EntityCollection entities = state.getEntityCollection();
352: if (entities != null) {
353: String tip = null;
354: CategoryToolTipGenerator tipster = getToolTipGenerator(
355: row, column);
356: if (tipster != null) {
357: tip = tipster.generateToolTip(dataset, row, column);
358: }
359: String url = null;
360: if (getItemURLGenerator(row, column) != null) {
361: url = getItemURLGenerator(row, column).generateURL(
362: dataset, row, column);
363: }
364: CategoryItemEntity entity = new CategoryItemEntity(line
365: .getBounds(), tip, url, dataset, dataset
366: .getRowKey(row), dataset.getColumnKey(column));
367: entities.add(entity);
368: }
369:
370: }
371:
372: }
373:
374: /**
375: * Calculates the available space for each series.
376: *
377: * @param space the space along the entire axis (in Java2D units).
378: * @param axis the category axis.
379: * @param categories the number of categories.
380: * @param series the number of series.
381: *
382: * @return The width of one series.
383: */
384: protected double calculateSeriesWidth(double space,
385: CategoryAxis axis, int categories, int series) {
386: double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
387: - axis.getUpperMargin();
388: if (categories > 1) {
389: factor = factor - axis.getCategoryMargin();
390: }
391: return (space * factor) / (categories * series);
392: }
393:
394: /**
395: * Tests an object for equality with this instance.
396: *
397: * @param obj the object (<code>null</code> permitted).
398: *
399: * @return A boolean.
400: */
401: public boolean equals(Object obj) {
402: if (obj == this ) {
403: return true;
404: }
405: if (!(obj instanceof LevelRenderer)) {
406: return false;
407: }
408: if (!super .equals(obj)) {
409: return false;
410: }
411: LevelRenderer that = (LevelRenderer) obj;
412: if (this .itemMargin != that.itemMargin) {
413: return false;
414: }
415: if (this .maxItemWidth != that.maxItemWidth) {
416: return false;
417: }
418: return true;
419: }
420:
421: }
|