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 * DeviationRenderer.java
029 * ----------------------
030 * (C) Copyright 2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * $Id: DeviationRenderer.java,v 1.1.2.3 2007/05/04 11:12:16 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 21-Feb-2007 : Version 1 (DG);
040 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
041 *
042 */
043
044 package org.jfree.chart.renderer.xy;
045
046 import java.awt.AlphaComposite;
047 import java.awt.Composite;
048 import java.awt.Graphics2D;
049 import java.awt.geom.GeneralPath;
050 import java.awt.geom.Rectangle2D;
051 import java.util.List;
052
053 import org.jfree.chart.axis.ValueAxis;
054 import org.jfree.chart.entity.EntityCollection;
055 import org.jfree.chart.event.RendererChangeEvent;
056 import org.jfree.chart.plot.CrosshairState;
057 import org.jfree.chart.plot.PlotOrientation;
058 import org.jfree.chart.plot.PlotRenderingInfo;
059 import org.jfree.chart.plot.XYPlot;
060 import org.jfree.data.xy.IntervalXYDataset;
061 import org.jfree.data.xy.XYDataset;
062 import org.jfree.ui.RectangleEdge;
063
064 /**
065 * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires
066 * an {@link IntervalXYDataset} and represents the y-interval by shading an
067 * area behind the y-values on the chart.
068 *
069 * @since 1.0.5
070 */
071 public class DeviationRenderer extends XYLineAndShapeRenderer {
072
073 /**
074 * A state object that is passed to each call to <code>drawItem</code>.
075 */
076 public static class State extends XYLineAndShapeRenderer.State {
077
078 /**
079 * A list of coordinates for the upper y-values in the current series
080 * (after translation into Java2D space).
081 */
082 public List upperCoordinates;
083
084 /**
085 * A list of coordinates for the lower y-values in the current series
086 * (after translation into Java2D space).
087 */
088 public List lowerCoordinates;
089
090 /**
091 * Creates a new state instance.
092 *
093 * @param info the plot rendering info.
094 */
095 public State(PlotRenderingInfo info) {
096 super(info);
097 this.lowerCoordinates = new java.util.ArrayList();
098 this.upperCoordinates = new java.util.ArrayList();
099 }
100
101 }
102
103 /** The alpha transparency for the interval shading. */
104 private float alpha;
105
106 /**
107 * Creates a new renderer that displays lines and shapes for the data
108 * items, as well as the shaded area for the y-interval.
109 */
110 public DeviationRenderer() {
111 this(true, true);
112 }
113
114 /**
115 * Creates a new renderer.
116 *
117 * @param lines show lines between data items?
118 * @param shapes show a shape for each data item?
119 */
120 public DeviationRenderer(boolean lines, boolean shapes) {
121 super(lines, shapes);
122 super.setDrawSeriesLineAsPath(true);
123 this.alpha = 0.5f;
124 }
125
126 /**
127 * Returns the alpha transparency for the background shading.
128 *
129 * @return The alpha transparency.
130 *
131 * @see #setAlpha(float)
132 */
133 public float getAlpha() {
134 return this.alpha;
135 }
136
137 /**
138 * Sets the alpha transparency for the background shading, and sends a
139 * {@link RendererChangeEvent} to all registered listeners.
140 *
141 * @param alpha the alpha (in the range 0.0f to 1.0f).
142 *
143 * @see #getAlpha()
144 */
145 public void setAlpha(float alpha) {
146 if (alpha < 0.0f || alpha > 1.0f) {
147 throw new IllegalArgumentException(
148 "Requires 'alpha' in the range 0.0 to 1.0.");
149 }
150 this.alpha = alpha;
151 notifyListeners(new RendererChangeEvent(this));
152 }
153
154 /**
155 * This method is overridden so that this flag cannot be changed---it is
156 * set to <code>true</code> for this renderer.
157 *
158 * @param flag ignored.
159 */
160 public void setDrawSeriesLineAsPath(boolean flag) {
161 // ignore
162 }
163
164 /**
165 * Initialises and returns a state object that can be passed to each
166 * invocation of the {@link #drawItem} method.
167 *
168 * @param g2 the graphics target.
169 * @param dataArea the data area.
170 * @param plot the plot.
171 * @param dataset the dataset.
172 * @param info the plot rendering info.
173 *
174 * @return A newly initialised state object.
175 */
176 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
177 XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
178 State state = new State(info);
179 state.seriesPath = new GeneralPath();
180 state.setProcessVisibleItemsOnly(false);
181 return state;
182 }
183
184 /**
185 * Returns the number of passes (through the dataset) used by this
186 * renderer.
187 *
188 * @return <code>3</code>.
189 */
190 public int getPassCount() {
191 return 3;
192 }
193
194 /**
195 * Returns <code>true</code> if this is the pass where the shapes are
196 * drawn.
197 *
198 * @param pass the pass index.
199 *
200 * @return A boolean.
201 *
202 * @see #isLinePass(int)
203 */
204 protected boolean isItemPass(int pass) {
205 return (pass == 2);
206 }
207
208 /**
209 * Returns <code>true</code> if this is the pass where the lines are
210 * drawn.
211 *
212 * @param pass the pass index.
213 *
214 * @return A boolean.
215 *
216 * @see #isItemPass(int)
217 */
218 protected boolean isLinePass(int pass) {
219 return (pass == 1);
220 }
221
222 /**
223 * Draws the visual representation of a single data item.
224 *
225 * @param g2 the graphics device.
226 * @param state the renderer state.
227 * @param dataArea the area within which the data is being drawn.
228 * @param info collects information about the drawing.
229 * @param plot the plot (can be used to obtain standard color
230 * information etc).
231 * @param domainAxis the domain axis.
232 * @param rangeAxis the range axis.
233 * @param dataset the dataset.
234 * @param series the series index (zero-based).
235 * @param item the item index (zero-based).
236 * @param crosshairState crosshair information for the plot
237 * (<code>null</code> permitted).
238 * @param pass the pass index.
239 */
240 public void drawItem(Graphics2D g2,
241 XYItemRendererState state,
242 Rectangle2D dataArea,
243 PlotRenderingInfo info,
244 XYPlot plot,
245 ValueAxis domainAxis,
246 ValueAxis rangeAxis,
247 XYDataset dataset,
248 int series,
249 int item,
250 CrosshairState crosshairState,
251 int pass) {
252
253 // do nothing if item is not visible
254 if (!getItemVisible(series, item)) {
255 return;
256 }
257
258 // first pass draws the shading
259 if (pass == 0) {
260 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
261 State drState = (State) state;
262
263 double x = intervalDataset.getXValue(series, item);
264 double yLow = intervalDataset.getStartYValue(series, item);
265 double yHigh = intervalDataset.getEndYValue(series, item);
266
267 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
268 RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
269
270 double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
271 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
272 yAxisLocation);
273 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
274 yAxisLocation);
275
276 PlotOrientation orientation = plot.getOrientation();
277 if (orientation == PlotOrientation.HORIZONTAL) {
278 drState.lowerCoordinates.add(new double[] {yyLow, xx});
279 drState.upperCoordinates.add(new double[] {yyHigh, xx});
280 }
281 else if (orientation == PlotOrientation.VERTICAL) {
282 drState.lowerCoordinates.add(new double[] {xx, yyLow});
283 drState.upperCoordinates.add(new double[] {xx, yyHigh});
284 }
285
286 if (item == (dataset.getItemCount(series) - 1)) {
287 // last item in series, draw the lot...
288 // set up the alpha-transparency...
289 Composite originalComposite = g2.getComposite();
290 g2.setComposite(AlphaComposite.getInstance(
291 AlphaComposite.SRC_OVER, this.alpha));
292 g2.setPaint(getItemFillPaint(series, item));
293 GeneralPath area = new GeneralPath();
294 double[] coords = (double[]) drState.lowerCoordinates.get(0);
295 area.moveTo((float) coords[0], (float) coords[1]);
296 for (int i = 1; i < drState.lowerCoordinates.size(); i++) {
297 coords = (double[]) drState.lowerCoordinates.get(i);
298 area.lineTo((float) coords[0], (float) coords[1]);
299 }
300 int count = drState.upperCoordinates.size();
301 coords = (double[]) drState.upperCoordinates.get(count - 1);
302 area.lineTo((float) coords[0], (float) coords[1]);
303 for (int i = count - 2; i >= 0; i--) {
304 coords = (double[]) drState.upperCoordinates.get(i);
305 area.lineTo((float) coords[0], (float) coords[1]);
306 }
307 area.closePath();
308 g2.fill(area);
309 g2.setComposite(originalComposite);
310
311 drState.lowerCoordinates.clear();
312 drState.upperCoordinates.clear();
313 }
314 }
315 if (isLinePass(pass)) {
316
317 // the following code handles the line for the y-values...it's
318 // all done by code in the super class
319 if (item == 0) {
320 State s = (State) state;
321 s.seriesPath.reset();
322 s.setLastPointGood(false);
323 }
324
325 if (getItemLineVisible(series, item)) {
326 drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
327 series, item, domainAxis, rangeAxis, dataArea);
328 }
329 }
330
331 // second pass adds shapes where the items are ..
332 else if (isItemPass(pass)) {
333
334 // setup for collecting optional entity info...
335 EntityCollection entities = null;
336 if (info != null) {
337 entities = info.getOwner().getEntityCollection();
338 }
339
340 drawSecondaryPass(g2, plot, dataset, pass, series, item,
341 domainAxis, dataArea, rangeAxis, crosshairState, entities);
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 DeviationRenderer)) {
357 return false;
358 }
359 DeviationRenderer that = (DeviationRenderer) obj;
360 if (this.alpha != that.alpha) {
361 return false;
362 }
363 return super.equals(obj);
364 }
365
366 }