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 * XYLine3DRenderer.java
029 * ---------------------
030 * (C) Copyright 2005, 2007, by Object Refinery Limited.
031 *
032 * Original Author:  Thomas Morgner;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *
035 * Changes
036 * -------
037 * 14-Jan-2005 : Added standard header (DG);
038 * 01-May-2007 : Fixed equals() and serialization bugs (DG);
039 * 
040 */
041
042package org.jfree.chart.renderer.xy;
043
044import java.awt.Color;
045import java.awt.Graphics2D;
046import java.awt.Paint;
047import java.awt.Shape;
048import java.io.IOException;
049import java.io.ObjectInputStream;
050import java.io.ObjectOutputStream;
051import java.io.Serializable;
052
053import org.jfree.chart.Effect3D;
054import org.jfree.chart.event.RendererChangeEvent;
055import org.jfree.io.SerialUtilities;
056import org.jfree.util.PaintUtilities;
057
058/**
059 * A XYLineAndShapeRenderer that adds a shadow line to the graph
060 * to emulate a 3D-effect.
061 */
062public class XYLine3DRenderer extends XYLineAndShapeRenderer 
063                              implements Effect3D, Serializable {
064
065    /** For serialization. */
066    private static final long serialVersionUID = 588933208243446087L;
067    
068    /** The default x-offset for the 3D effect. */
069    public static final double DEFAULT_X_OFFSET = 12.0;
070
071    /** The default y-offset for the 3D effect. */
072    public static final double DEFAULT_Y_OFFSET = 8.0;
073
074    /** The default wall paint. */
075    public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
076
077    /** The size of x-offset for the 3D effect. */
078    private double xOffset;
079
080    /** The size of y-offset for the 3D effect. */
081    private double yOffset;
082
083    /** The paint used to shade the left and lower 3D wall. */
084    private transient Paint wallPaint;
085
086    /**
087     * Creates a new renderer.
088     */
089    public XYLine3DRenderer() {
090        this.wallPaint = DEFAULT_WALL_PAINT;
091        this.xOffset = DEFAULT_X_OFFSET;
092        this.yOffset = DEFAULT_Y_OFFSET;
093    }
094
095    /**
096     * Returns the x-offset for the 3D effect.
097     *
098     * @return The 3D effect.
099     */
100    public double getXOffset() {
101        return this.xOffset;
102    }
103
104    /**
105     * Returns the y-offset for the 3D effect.
106     *
107     * @return The 3D effect.
108     */
109    public double getYOffset() {
110        return this.yOffset;
111    }
112
113    /**
114     * Sets the x-offset and sends a {@link RendererChangeEvent} to all 
115     * registered listeners.
116     * 
117     * @param xOffset  the x-offset.
118     */
119    public void setXOffset(double xOffset) {
120        this.xOffset = xOffset;
121        notifyListeners(new RendererChangeEvent(this));
122    }
123
124    /**
125     * Sets the y-offset and sends a {@link RendererChangeEvent} to all 
126     * registered listeners.
127     * 
128     * @param yOffset  the y-offset.
129     */
130    public void setYOffset(double yOffset) {
131        this.yOffset = yOffset;
132        notifyListeners(new RendererChangeEvent(this));
133    }
134
135    /**
136     * Returns the paint used to highlight the left and bottom wall in the plot
137     * background.
138     *
139     * @return The paint.
140     */
141    public Paint getWallPaint() {
142        return this.wallPaint;
143    }
144
145    /**
146     * Sets the paint used to hightlight the left and bottom walls in the plot 
147     * background.
148     *
149     * @param paint  the paint.
150     */
151    public void setWallPaint(Paint paint) {
152        this.wallPaint = paint;
153        notifyListeners(new RendererChangeEvent(this));
154    }
155
156    /**
157     * Returns the number of passes through the data that the renderer requires 
158     * in order to draw the chart.  Most charts will require a single pass, 
159     * but some require two passes.
160     *
161     * @return The pass count.
162     */
163    public int getPassCount() {
164        return 3;
165    }
166
167    /**
168     * Returns <code>true</code> if the specified pass involves drawing lines.
169     * 
170     * @param pass  the pass.
171     * 
172     * @return A boolean.
173     */
174    protected boolean isLinePass(int pass) {
175        return pass == 0 || pass == 1;
176    }
177
178    /**
179     * Returns <code>true</code> if the specified pass involves drawing items.
180     * 
181     * @param pass  the pass.
182     * 
183     * @return A boolean.
184     */
185    protected boolean isItemPass(int pass) {
186        return pass == 2;
187    }
188
189    /**
190     * Returns <code>true</code> if the specified pass involves drawing shadows.
191     * 
192     * @param pass  the pass.
193     * 
194     * @return A boolean.
195     */
196    protected boolean isShadowPass (int pass) {
197        return pass == 0;
198    }
199
200    /**
201     * Overrides the method in the subclass to draw a shadow in the first pass.
202     * 
203     * @param g2  the graphics device.
204     * @param pass  the pass.
205     * @param series  the series index (zero-based).
206     * @param item  the item index (zero-based).
207     * @param shape  the shape.
208     */
209    protected void drawFirstPassShape(Graphics2D g2,
210                                      int pass,
211                                      int series,
212                                      int item,
213                                      Shape shape) {
214        if (isShadowPass(pass)) {
215            if (getWallPaint() != null) {
216                g2.setStroke(getItemStroke(series, item));
217                g2.setPaint(getWallPaint());
218                g2.translate(getXOffset(), getYOffset());
219                g2.draw(shape);
220                g2.translate(-getXOffset(), -getYOffset());
221            }
222        }
223        else {
224            // now draw the real shape
225            super.drawFirstPassShape(g2, pass, series, item, shape);
226        }
227    }
228
229    /**
230     * Tests this renderer for equality with an arbitrary object.
231     * 
232     * @param obj  the object (<code>null</code> permitted).
233     * 
234     * @return A boolean.
235     */
236    public boolean equals(Object obj) {
237        if (obj == this) {
238            return true;
239        }
240        if (!(obj instanceof XYLine3DRenderer)) {
241            return false;
242        }
243        XYLine3DRenderer that = (XYLine3DRenderer) obj;
244        if (this.xOffset != that.xOffset) {
245            return false;
246        }
247        if (this.yOffset != that.yOffset) {
248            return false;
249        }
250        if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
251            return false;
252        }
253        return super.equals(obj);
254    }
255    
256    /**
257     * Provides serialization support.
258     *
259     * @param stream  the input stream.
260     *
261     * @throws IOException  if there is an I/O error.
262     * @throws ClassNotFoundException  if there is a classpath problem.
263     */
264    private void readObject(ObjectInputStream stream) 
265            throws IOException, ClassNotFoundException {
266        stream.defaultReadObject();
267        this.wallPaint = SerialUtilities.readPaint(stream);
268    }
269    
270    /**
271     * Provides serialization support.
272     *
273     * @param stream  the output stream.
274     *
275     * @throws IOException  if there is an I/O error.
276     */
277    private void writeObject(ObjectOutputStream stream) throws IOException {
278        stream.defaultWriteObject();
279        SerialUtilities.writePaint(this.wallPaint, stream);
280    }
281
282}