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 * XYLine3DRenderer.java
029 * ---------------------
030 * (C) Copyright 2005-2008, 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        fireChangeEvent();
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        fireChangeEvent();
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 and sends a {@link RendererChangeEvent} to all registered
148     * listeners.
149     *
150     * @param paint  the paint.
151     */
152    public void setWallPaint(Paint paint) {
153        this.wallPaint = paint;
154        fireChangeEvent();
155    }
156
157    /**
158     * Returns the number of passes through the data that the renderer requires
159     * in order to draw the chart.  Most charts will require a single pass,
160     * but some require two passes.
161     *
162     * @return The pass count.
163     */
164    public int getPassCount() {
165        return 3;
166    }
167
168    /**
169     * Returns <code>true</code> if the specified pass involves drawing lines.
170     *
171     * @param pass  the pass.
172     *
173     * @return A boolean.
174     */
175    protected boolean isLinePass(int pass) {
176        return pass == 0 || pass == 1;
177    }
178
179    /**
180     * Returns <code>true</code> if the specified pass involves drawing items.
181     *
182     * @param pass  the pass.
183     *
184     * @return A boolean.
185     */
186    protected boolean isItemPass(int pass) {
187        return pass == 2;
188    }
189
190    /**
191     * Returns <code>true</code> if the specified pass involves drawing shadows.
192     *
193     * @param pass  the pass.
194     *
195     * @return A boolean.
196     */
197    protected boolean isShadowPass (int pass) {
198        return pass == 0;
199    }
200
201    /**
202     * Overrides the method in the subclass to draw a shadow in the first pass.
203     *
204     * @param g2  the graphics device.
205     * @param pass  the pass.
206     * @param series  the series index (zero-based).
207     * @param item  the item index (zero-based).
208     * @param shape  the shape.
209     */
210    protected void drawFirstPassShape(Graphics2D g2,
211                                      int pass,
212                                      int series,
213                                      int item,
214                                      Shape shape) {
215        if (isShadowPass(pass)) {
216            if (getWallPaint() != null) {
217                g2.setStroke(getItemStroke(series, item));
218                g2.setPaint(getWallPaint());
219                g2.translate(getXOffset(), getYOffset());
220                g2.draw(shape);
221                g2.translate(-getXOffset(), -getYOffset());
222            }
223        }
224        else {
225            // now draw the real shape
226            super.drawFirstPassShape(g2, pass, series, item, shape);
227        }
228    }
229
230    /**
231     * Tests this renderer for equality with an arbitrary object.
232     *
233     * @param obj  the object (<code>null</code> permitted).
234     *
235     * @return A boolean.
236     */
237    public boolean equals(Object obj) {
238        if (obj == this) {
239            return true;
240        }
241        if (!(obj instanceof XYLine3DRenderer)) {
242            return false;
243        }
244        XYLine3DRenderer that = (XYLine3DRenderer) obj;
245        if (this.xOffset != that.xOffset) {
246            return false;
247        }
248        if (this.yOffset != that.yOffset) {
249            return false;
250        }
251        if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
252            return false;
253        }
254        return super.equals(obj);
255    }
256
257    /**
258     * Provides serialization support.
259     *
260     * @param stream  the input stream.
261     *
262     * @throws IOException  if there is an I/O error.
263     * @throws ClassNotFoundException  if there is a classpath problem.
264     */
265    private void readObject(ObjectInputStream stream)
266            throws IOException, ClassNotFoundException {
267        stream.defaultReadObject();
268        this.wallPaint = SerialUtilities.readPaint(stream);
269    }
270
271    /**
272     * Provides serialization support.
273     *
274     * @param stream  the output stream.
275     *
276     * @throws IOException  if there is an I/O error.
277     */
278    private void writeObject(ObjectOutputStream stream) throws IOException {
279        stream.defaultWriteObject();
280        SerialUtilities.writePaint(this.wallPaint, stream);
281    }
282
283}