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