1 /* ===========================================================
2 * JFreeChart : a free chart library for the Java(tm) platform
3 * ===========================================================
4 *
5 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
6 *
7 * Project Info: http://www.jfree.org/jfreechart/index.html
8 *
9 * This library is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17 * License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * USA.
23 *
24 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25 * in the United States and other countries.]
26 *
27 * ------------------
28 * LevelRenderer.java
29 * ------------------
30 * (C) Copyright 2004-2008, by Object Refinery Limited.
31 *
32 * Original Author: David Gilbert (for Object Refinery Limited);
33 * Contributor(s): -;
34 *
35 * Changes
36 * -------
37 * 09-Jan-2004 : Version 1 (DG);
38 * 05-Nov-2004 : Modified drawItem() signature (DG);
39 * 20-Apr-2005 : Renamed CategoryLabelGenerator
40 * --> CategoryItemLabelGenerator (DG);
41 * ------------- JFREECHART 1.0.x ---------------------------------------------
42 * 23-Jan-2006 : Renamed getMaxItemWidth() --> getMaximumItemWidth() (DG);
43 * 13-May-2008 : Code clean-up (DG);
44 *
45 */
46
47 package org.jfree.chart.renderer.category;
48
49 import java.awt.Graphics2D;
50 import java.awt.Paint;
51 import java.awt.Stroke;
52 import java.awt.geom.Line2D;
53 import java.awt.geom.Rectangle2D;
54 import java.io.Serializable;
55
56 import org.jfree.chart.axis.CategoryAxis;
57 import org.jfree.chart.axis.ValueAxis;
58 import org.jfree.chart.entity.EntityCollection;
59 import org.jfree.chart.event.RendererChangeEvent;
60 import org.jfree.chart.labels.CategoryItemLabelGenerator;
61 import org.jfree.chart.plot.CategoryPlot;
62 import org.jfree.chart.plot.PlotOrientation;
63 import org.jfree.chart.plot.PlotRenderingInfo;
64 import org.jfree.data.category.CategoryDataset;
65 import org.jfree.ui.RectangleEdge;
66 import org.jfree.util.PublicCloneable;
67
68 /**
69 * A {@link CategoryItemRenderer} that draws individual data items as
70 * horizontal lines, spaced in the same way as bars in a bar chart.
71 */
72 public class LevelRenderer extends AbstractCategoryItemRenderer
73 implements Cloneable, PublicCloneable, Serializable {
74
75 /** For serialization. */
76 private static final long serialVersionUID = -8204856624355025117L;
77
78 /** The default item margin percentage. */
79 public static final double DEFAULT_ITEM_MARGIN = 0.20;
80
81 /** The margin between items within a category. */
82 private double itemMargin;
83
84 /** The maximum item width as a percentage of the available space. */
85 private double maxItemWidth;
86
87 /**
88 * Creates a new renderer with default settings.
89 */
90 public LevelRenderer() {
91 super();
92 this.itemMargin = DEFAULT_ITEM_MARGIN;
93 this.maxItemWidth = 1.0; // 100 percent, so it will not apply unless
94 // changed
95 }
96
97 /**
98 * Returns the item margin.
99 *
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 }