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 * CategoryStepRenderer.java
029 * -------------------------
030 *
031 * (C) Copyright 2004-2007, by Brian Cole and Contributors.
032 *
033 * Original Author: Brian Cole;
034 * Contributor(s): David Gilbert (for Object Refinery Limited);
035 *
036 * $Id: CategoryStepRenderer.java,v 1.5.2.5 2007/05/18 10:28:27 mungady Exp $
037 *
038 * Changes
039 * -------
040 * 21-Apr-2004 : Version 1, contributed by Brian Cole (DG);
041 * 22-Apr-2004 : Fixed Checkstyle complaints (DG);
042 * 05-Nov-2004 : Modified drawItem() signature (DG);
043 * 08-Mar-2005 : Added equals() method (DG);
044 * ------------- JFREECHART 1.0.x ---------------------------------------------
045 * 30-Nov-2006 : Added checks for series visibility (DG);
046 * 22-Feb-2007 : Use new state object for reusable line, enable chart entities
047 * (for tooltips, URLs), added new getLegendItem() override (DG);
048 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
049 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
050 *
051 */
052
053 package org.jfree.chart.renderer.category;
054
055 import java.awt.Graphics2D;
056 import java.awt.Paint;
057 import java.awt.Shape;
058 import java.awt.geom.Line2D;
059 import java.awt.geom.Rectangle2D;
060 import java.io.Serializable;
061
062 import org.jfree.chart.LegendItem;
063 import org.jfree.chart.axis.CategoryAxis;
064 import org.jfree.chart.axis.ValueAxis;
065 import org.jfree.chart.entity.EntityCollection;
066 import org.jfree.chart.event.RendererChangeEvent;
067 import org.jfree.chart.plot.CategoryPlot;
068 import org.jfree.chart.plot.PlotOrientation;
069 import org.jfree.chart.plot.PlotRenderingInfo;
070 import org.jfree.chart.renderer.xy.XYStepRenderer;
071 import org.jfree.data.category.CategoryDataset;
072 import org.jfree.util.PublicCloneable;
073
074 /**
075 * A "step" renderer similar to {@link XYStepRenderer} but
076 * that can be used with the {@link CategoryPlot} class.
077 */
078 public class CategoryStepRenderer extends AbstractCategoryItemRenderer
079 implements Cloneable, PublicCloneable,
080 Serializable {
081
082 /**
083 * State information for the renderer.
084 */
085 protected static class State extends CategoryItemRendererState {
086
087 /**
088 * A working line for re-use to avoid creating large numbers of
089 * objects.
090 */
091 public Line2D line;
092
093 /**
094 * Creates a new state instance.
095 *
096 * @param info collects plot rendering information (<code>null</code>
097 * permitted).
098 */
099 public State(PlotRenderingInfo info) {
100 super(info);
101 this.line = new Line2D.Double();
102 }
103
104 }
105
106 /** For serialization. */
107 private static final long serialVersionUID = -5121079703118261470L;
108
109 /** The stagger width. */
110 public static final int STAGGER_WIDTH = 5; // could make this configurable
111
112 /**
113 * A flag that controls whether or not the steps for multiple series are
114 * staggered.
115 */
116 private boolean stagger = false;
117
118 /**
119 * Creates a new renderer (stagger defaults to <code>false</code>).
120 */
121 public CategoryStepRenderer() {
122 this(false);
123 }
124
125 /**
126 * Creates a new renderer.
127 *
128 * @param stagger should the horizontal part of the step be staggered by
129 * series?
130 */
131 public CategoryStepRenderer(boolean stagger) {
132 this.stagger = stagger;
133 }
134
135 /**
136 * Returns the flag that controls whether the series steps are staggered.
137 *
138 * @return A boolean.
139 */
140 public boolean getStagger() {
141 return this.stagger;
142 }
143
144 /**
145 * Sets the flag that controls whether or not the series steps are
146 * staggered and sends a {@link RendererChangeEvent} to all registered
147 * listeners.
148 *
149 * @param shouldStagger a boolean.
150 */
151 public void setStagger(boolean shouldStagger) {
152 this.stagger = shouldStagger;
153 notifyListeners(new RendererChangeEvent(this));
154 }
155
156 /**
157 * Returns a legend item for a series.
158 *
159 * @param datasetIndex the dataset index (zero-based).
160 * @param series the series index (zero-based).
161 *
162 * @return The legend item.
163 */
164 public LegendItem getLegendItem(int datasetIndex, int series) {
165
166 CategoryPlot p = getPlot();
167 if (p == null) {
168 return null;
169 }
170
171 // check that a legend item needs to be displayed...
172 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
173 return null;
174 }
175
176 CategoryDataset dataset = p.getDataset(datasetIndex);
177 String label = getLegendItemLabelGenerator().generateLabel(dataset,
178 series);
179 String description = label;
180 String toolTipText = null;
181 if (getLegendItemToolTipGenerator() != null) {
182 toolTipText = getLegendItemToolTipGenerator().generateLabel(
183 dataset, series);
184 }
185 String urlText = null;
186 if (getLegendItemURLGenerator() != null) {
187 urlText = getLegendItemURLGenerator().generateLabel(dataset,
188 series);
189 }
190 Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0);
191 Paint paint = lookupSeriesPaint(series);
192
193 LegendItem item = new LegendItem(label, description, toolTipText,
194 urlText, shape, paint);
195 item.setSeriesKey(dataset.getRowKey(series));
196 item.setSeriesIndex(series);
197 item.setDataset(dataset);
198 item.setDatasetIndex(datasetIndex);
199 return item;
200 }
201
202 /**
203 * Creates a new state instance. This method is called from
204 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int,
205 * PlotRenderingInfo)}, and we override it to ensure that the state
206 * contains a working Line2D instance.
207 *
208 * @param info the plot rendering info (<code>null</code> is permitted).
209 *
210 * @return A new state instance.
211 */
212 protected CategoryItemRendererState createState(PlotRenderingInfo info) {
213 return new State(info);
214 }
215
216 /**
217 * Draws a line taking into account the specified orientation.
218 * <p>
219 * In version 1.0.5, the signature of this method was changed by the
220 * addition of the 'state' parameter. This is an incompatible change, but
221 * is considered a low risk because it is unlikely that anyone has
222 * subclassed this renderer. If this *does* cause trouble for you, please
223 * report it as a bug.
224 *
225 * @param g2 the graphics device.
226 * @param state the renderer state.
227 * @param orientation the plot orientation.
228 * @param x0 the x-coordinate for the start of the line.
229 * @param y0 the y-coordinate for the start of the line.
230 * @param x1 the x-coordinate for the end of the line.
231 * @param y1 the y-coordinate for the end of the line.
232 */
233 protected void drawLine(Graphics2D g2, State state,
234 PlotOrientation orientation, double x0, double y0, double x1,
235 double y1) {
236
237 if (orientation == PlotOrientation.VERTICAL) {
238 state.line.setLine(x0, y0, x1, y1);
239 g2.draw(state.line);
240 }
241 else if (orientation == PlotOrientation.HORIZONTAL) {
242 state.line.setLine(y0, x0, y1, x1); // switch x and y
243 g2.draw(state.line);
244 }
245
246 }
247
248 /**
249 * Draw a single data item.
250 *
251 * @param g2 the graphics device.
252 * @param state the renderer state.
253 * @param dataArea the area in which the data is drawn.
254 * @param plot the plot.
255 * @param domainAxis the domain axis.
256 * @param rangeAxis the range axis.
257 * @param dataset the dataset.
258 * @param row the row index (zero-based).
259 * @param column the column index (zero-based).
260 * @param pass the pass index.
261 */
262 public void drawItem(Graphics2D g2,
263 CategoryItemRendererState state,
264 Rectangle2D dataArea,
265 CategoryPlot plot,
266 CategoryAxis domainAxis,
267 ValueAxis rangeAxis,
268 CategoryDataset dataset,
269 int row,
270 int column,
271 int pass) {
272
273 // do nothing if item is not visible
274 if (!getItemVisible(row, column)) {
275 return;
276 }
277
278 Number value = dataset.getValue(row, column);
279 if (value == null) {
280 return;
281 }
282 PlotOrientation orientation = plot.getOrientation();
283
284 // current data point...
285 double x1s = domainAxis.getCategoryStart(column, getColumnCount(),
286 dataArea, plot.getDomainAxisEdge());
287 double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
288 dataArea, plot.getDomainAxisEdge());
289 double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
290 double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea,
291 plot.getRangeAxisEdge());
292 g2.setPaint(getItemPaint(row, column));
293 g2.setStroke(getItemStroke(row, column));
294
295 if (column != 0) {
296 Number previousValue = dataset.getValue(row, column - 1);
297 if (previousValue != null) {
298 // previous data point...
299 double previous = previousValue.doubleValue();
300 double x0s = domainAxis.getCategoryStart(column - 1,
301 getColumnCount(), dataArea, plot.getDomainAxisEdge());
302 double x0 = domainAxis.getCategoryMiddle(column - 1,
303 getColumnCount(), dataArea, plot.getDomainAxisEdge());
304 double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
305 double y0 = rangeAxis.valueToJava2D(previous, dataArea,
306 plot.getRangeAxisEdge());
307 if (getStagger()) {
308 int xStagger = row * STAGGER_WIDTH;
309 if (xStagger > (x1s - x0e)) {
310 xStagger = (int) (x1s - x0e);
311 }
312 x1s = x0e + xStagger;
313 }
314 drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0);
315 // extend x0's flat bar
316
317 drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1);
318 // upright bar
319 }
320 }
321 drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1);
322 // x1's flat bar
323
324 // draw the item labels if there are any...
325 if (isItemLabelVisible(row, column)) {
326 drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
327 (value.doubleValue() < 0.0));
328 }
329
330 // add an item entity, if this information is being collected
331 EntityCollection entities = state.getEntityCollection();
332 if (entities != null) {
333 Rectangle2D hotspot = new Rectangle2D.Double();
334 if (orientation == PlotOrientation.VERTICAL) {
335 hotspot.setRect(x1s, y1, x1e - x1s, 4.0);
336 }
337 else {
338 hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s);
339 }
340 addItemEntity(entities, dataset, row, column, hotspot);
341 }
342
343 }
344
345 /**
346 * Tests this renderer for equality with an arbitrary object.
347 *
348 * @param obj the object (<code>null</code> permitted).
349 *
350 * @return A boolean.
351 */
352 public boolean equals(Object obj) {
353 if (obj == this) {
354 return true;
355 }
356 if (!(obj instanceof CategoryStepRenderer)) {
357 return false;
358 }
359 CategoryStepRenderer that = (CategoryStepRenderer) obj;
360 if (this.stagger != that.stagger) {
361 return false;
362 }
363 return super.equals(obj);
364 }
365
366 }