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