001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2006, 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, 2006, 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.3 2006/01/23 09:53:58 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,
186 CategoryPlot plot,
187 int rendererIndex,
188 PlotRenderingInfo info) {
189
190 CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
191 rendererIndex, info);
192 calculateItemWidth(plot, dataArea, rendererIndex, state);
193 return state;
194
195 }
196
197 /**
198 * Calculates the bar width and stores it in the renderer state.
199 *
200 * @param plot the plot.
201 * @param dataArea the data area.
202 * @param rendererIndex the renderer index.
203 * @param state the renderer state.
204 */
205 protected void calculateItemWidth(CategoryPlot plot,
206 Rectangle2D dataArea,
207 int rendererIndex,
208 CategoryItemRendererState state) {
209
210 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
211 CategoryDataset dataset = plot.getDataset(rendererIndex);
212 if (dataset != null) {
213 int columns = dataset.getColumnCount();
214 int rows = dataset.getRowCount();
215 double space = 0.0;
216 PlotOrientation orientation = plot.getOrientation();
217 if (orientation == PlotOrientation.HORIZONTAL) {
218 space = dataArea.getHeight();
219 }
220 else if (orientation == PlotOrientation.VERTICAL) {
221 space = dataArea.getWidth();
222 }
223 double maxWidth = space * getMaxItemWidth();
224 double categoryMargin = 0.0;
225 double currentItemMargin = 0.0;
226 if (columns > 1) {
227 categoryMargin = domainAxis.getCategoryMargin();
228 }
229 if (rows > 1) {
230 currentItemMargin = getItemMargin();
231 }
232 double used = space * (1 - domainAxis.getLowerMargin()
233 - domainAxis.getUpperMargin()
234 - categoryMargin - currentItemMargin);
235 if ((rows * columns) > 0) {
236 state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
237 }
238 else {
239 state.setBarWidth(Math.min(used, maxWidth));
240 }
241 }
242 }
243
244 /**
245 * Calculates the coordinate of the first "side" of a bar. This will be
246 * the minimum x-coordinate for a vertical bar, and the minimum
247 * y-coordinate for a horizontal bar.
248 *
249 * @param plot the plot.
250 * @param orientation the plot orientation.
251 * @param dataArea the data area.
252 * @param domainAxis the domain axis.
253 * @param state the renderer state (has the bar width precalculated).
254 * @param row the row index.
255 * @param column the column index.
256 *
257 * @return The coordinate.
258 */
259 protected double calculateBarW0(CategoryPlot plot,
260 PlotOrientation orientation,
261 Rectangle2D dataArea,
262 CategoryAxis domainAxis,
263 CategoryItemRendererState state,
264 int row,
265 int column) {
266 // calculate bar width...
267 double space = 0.0;
268 if (orientation == PlotOrientation.HORIZONTAL) {
269 space = dataArea.getHeight();
270 }
271 else {
272 space = dataArea.getWidth();
273 }
274 double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
275 dataArea, plot.getDomainAxisEdge());
276 int seriesCount = getRowCount();
277 int categoryCount = getColumnCount();
278 if (seriesCount > 1) {
279 double seriesGap = space * getItemMargin()
280 / (categoryCount * (seriesCount - 1));
281 double seriesW = calculateSeriesWidth(space, domainAxis,
282 categoryCount, seriesCount);
283 barW0 = barW0 + row * (seriesW + seriesGap)
284 + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
285 }
286 else {
287 barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
288 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
289 / 2.0;
290 }
291 return barW0;
292 }
293
294 /**
295 * Draws the bar for a single (series, category) data item.
296 *
297 * @param g2 the graphics device.
298 * @param state the renderer state.
299 * @param dataArea the data area.
300 * @param plot the plot.
301 * @param domainAxis the domain axis.
302 * @param rangeAxis the range axis.
303 * @param dataset the dataset.
304 * @param row the row index (zero-based).
305 * @param column the column index (zero-based).
306 * @param pass the pass index.
307 */
308 public void drawItem(Graphics2D g2, CategoryItemRendererState state,
309 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
310 ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
311 int pass) {
312
313 // nothing is drawn for null values...
314 Number dataValue = dataset.getValue(row, column);
315 if (dataValue == null) {
316 return;
317 }
318
319 double value = dataValue.doubleValue();
320
321 PlotOrientation orientation = plot.getOrientation();
322 double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
323 state, row, column);
324 RectangleEdge edge = plot.getRangeAxisEdge();
325 double barL = rangeAxis.valueToJava2D(value, dataArea, edge);
326
327 // draw the bar...
328 Line2D line = null;
329 double x = 0.0;
330 double y = 0.0;
331 if (orientation == PlotOrientation.HORIZONTAL) {
332 x = barL;
333 y = barW0 + state.getBarWidth() / 2.0;
334 line = new Line2D.Double(barL, barW0, barL,
335 barW0 + state.getBarWidth());
336 }
337 else {
338 x = barW0 + state.getBarWidth() / 2.0;
339 y = barL;
340 line = new Line2D.Double(barW0, barL, barW0 + state.getBarWidth(),
341 barL);
342 }
343 Stroke itemStroke = getItemStroke(row, column);
344 Paint itemPaint = getItemPaint(row, column);
345 g2.setStroke(itemStroke);
346 g2.setPaint(itemPaint);
347 g2.draw(line);
348
349 CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
350 column);
351 if (generator != null && isItemLabelVisible(row, column)) {
352 drawItemLabel(g2, orientation, dataset, row, column, x, y,
353 (value < 0.0));
354 }
355
356 // collect entity and tool tip information...
357 if (state.getInfo() != null) {
358 EntityCollection entities = state.getEntityCollection();
359 if (entities != null) {
360 String tip = null;
361 CategoryToolTipGenerator tipster = getToolTipGenerator(row,
362 column);
363 if (tipster != null) {
364 tip = tipster.generateToolTip(dataset, row, column);
365 }
366 String url = null;
367 if (getItemURLGenerator(row, column) != null) {
368 url = getItemURLGenerator(row, column).generateURL(dataset,
369 row, column);
370 }
371 CategoryItemEntity entity = new CategoryItemEntity(
372 line.getBounds(), tip, url, dataset, row,
373 dataset.getColumnKey(column), column);
374 entities.add(entity);
375 }
376
377 }
378
379 }
380
381 /**
382 * Calculates the available space for each series.
383 *
384 * @param space the space along the entire axis (in Java2D units).
385 * @param axis the category axis.
386 * @param categories the number of categories.
387 * @param series the number of series.
388 *
389 * @return The width of one series.
390 */
391 protected double calculateSeriesWidth(double space, CategoryAxis axis,
392 int categories, int series) {
393 double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
394 - axis.getUpperMargin();
395 if (categories > 1) {
396 factor = factor - axis.getCategoryMargin();
397 }
398 return (space * factor) / (categories * series);
399 }
400
401 /**
402 * Tests an object for equality with this instance.
403 *
404 * @param obj the object (<code>null</code> permitted).
405 *
406 * @return A boolean.
407 */
408 public boolean equals(Object obj) {
409 if (obj == this) {
410 return true;
411 }
412 if (!(obj instanceof LevelRenderer)) {
413 return false;
414 }
415 if (!super.equals(obj)) {
416 return false;
417 }
418 LevelRenderer that = (LevelRenderer) obj;
419 if (this.itemMargin != that.itemMargin) {
420 return false;
421 }
422 if (this.maxItemWidth != that.maxItemWidth) {
423 return false;
424 }
425 return true;
426 }
427
428 }