001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------------
028 * StandardDialFrame.java
029 * ----------------------
030 * (C) Copyright 2006-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.plot.dial;
038
039import java.awt.BasicStroke;
040import java.awt.Color;
041import java.awt.Graphics2D;
042import java.awt.Paint;
043import java.awt.Shape;
044import java.awt.Stroke;
045import java.awt.geom.Area;
046import java.awt.geom.Ellipse2D;
047import java.awt.geom.Rectangle2D;
048import java.io.IOException;
049import java.io.ObjectInputStream;
050import java.io.ObjectOutputStream;
051import java.io.Serializable;
052
053import org.jfree.chart.HashUtils;
054import org.jfree.chart.util.PaintUtils;
055import org.jfree.chart.util.Args;
056import org.jfree.chart.util.PublicCloneable;
057import org.jfree.chart.util.SerialUtils;
058
059/**
060 * A simple circular frame for the {@link DialPlot} class.
061 */
062public class StandardDialFrame extends AbstractDialLayer implements DialFrame,
063        Cloneable, PublicCloneable, Serializable {
064
065    /** For serialization. */
066    static final long serialVersionUID = 1016585407507121596L;
067
068    /** The outer radius, relative to the framing rectangle. */
069    private double radius;
070
071    /**
072     * The color used for the front of the panel.  This field is transient
073     * because it requires special handling for serialization.
074     */
075    private transient Paint backgroundPaint;
076
077    /**
078     * The color used for the border around the window. This field is transient
079     * because it requires special handling for serialization.
080     */
081    private transient Paint foregroundPaint;
082
083    /**
084     * The stroke for drawing the frame outline.  This field is transient
085     * because it requires special handling for serialization.
086     */
087    private transient Stroke stroke;
088
089    /**
090     * Creates a new instance of {@code StandardDialFrame}.
091     */
092    public StandardDialFrame() {
093        this.backgroundPaint = Color.GRAY;
094        this.foregroundPaint = Color.BLACK;
095        this.stroke = new BasicStroke(2.0f);
096        this.radius = 0.95;
097    }
098
099    /**
100     * Returns the radius, relative to the framing rectangle.
101     *
102     * @return The radius.
103     *
104     * @see #setRadius(double)
105     */
106    public double getRadius() {
107        return this.radius;
108    }
109
110    /**
111     * Sets the radius and sends a {@link DialLayerChangeEvent} to all
112     * registered listeners.
113     *
114     * @param radius  the radius (must be positive).
115     *
116     * @see #getRadius()
117     */
118    public void setRadius(double radius) {
119        if (radius <= 0) {
120            throw new IllegalArgumentException(
121                    "The 'radius' must be positive.");
122        }
123        this.radius = radius;
124        notifyListeners(new DialLayerChangeEvent(this));
125    }
126
127    /**
128     * Returns the background paint.
129     *
130     * @return The background paint (never {@code null}).
131     *
132     * @see #setBackgroundPaint(Paint)
133     */
134    public Paint getBackgroundPaint() {
135        return this.backgroundPaint;
136    }
137
138    /**
139     * Sets the background paint and sends a {@link DialLayerChangeEvent} to
140     * all registered listeners.
141     *
142     * @param paint  the paint ({@code null} not permitted).
143     *
144     * @see #getBackgroundPaint()
145     */
146    public void setBackgroundPaint(Paint paint) {
147        Args.nullNotPermitted(paint, "paint");
148        this.backgroundPaint = paint;
149        notifyListeners(new DialLayerChangeEvent(this));
150    }
151
152    /**
153     * Returns the foreground paint.
154     *
155     * @return The foreground paint (never {@code null}).
156     *
157     * @see #setForegroundPaint(Paint)
158     */
159    public Paint getForegroundPaint() {
160        return this.foregroundPaint;
161    }
162
163    /**
164     * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to
165     * all registered listeners.
166     *
167     * @param paint  the paint ({@code null} not permitted).
168     *
169     * @see #getForegroundPaint()
170     */
171    public void setForegroundPaint(Paint paint) {
172        Args.nullNotPermitted(paint, "paint");
173        this.foregroundPaint = paint;
174        notifyListeners(new DialLayerChangeEvent(this));
175    }
176
177    /**
178     * Returns the stroke for the frame.
179     *
180     * @return The stroke (never {@code null}).
181     *
182     * @see #setStroke(Stroke)
183     */
184    public Stroke getStroke() {
185        return this.stroke;
186    }
187
188    /**
189     * Sets the stroke and sends a {@link DialLayerChangeEvent} to all
190     * registered listeners.
191     *
192     * @param stroke  the stroke ({@code null} not permitted).
193     *
194     * @see #getStroke()
195     */
196    public void setStroke(Stroke stroke) {
197        Args.nullNotPermitted(stroke, "stroke");
198        this.stroke = stroke;
199        notifyListeners(new DialLayerChangeEvent(this));
200    }
201
202    /**
203     * Returns the shape for the window for this dial.  Some dial layers will
204     * request that their drawing be clipped within this window.
205     *
206     * @param frame  the reference frame ({@code null} not permitted).
207     *
208     * @return The shape of the dial's window.
209     */
210    @Override
211    public Shape getWindow(Rectangle2D frame) {
212        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
213                this.radius);
214        return new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
215                f.getHeight());
216    }
217
218    /**
219     * Returns {@code false} to indicate that this dial layer is not
220     * clipped to the dial window.
221     *
222     * @return A boolean.
223     */
224    @Override
225    public boolean isClippedToWindow() {
226        return false;
227    }
228
229    /**
230     * Draws the frame.  This method is called by the {@link DialPlot} class,
231     * you shouldn't need to call it directly.
232     *
233     * @param g2  the graphics target ({@code null} not permitted).
234     * @param plot  the plot ({@code null} not permitted).
235     * @param frame  the frame ({@code null} not permitted).
236     * @param view  the view ({@code null} not permitted).
237     */
238    @Override
239    public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
240            Rectangle2D view) {
241
242        Shape window = getWindow(frame);
243
244        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius + 0.02,
245                this.radius + 0.02);
246        Ellipse2D e = new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
247                f.getHeight());
248
249        Area area = new Area(e);
250        Area area2 = new Area(window);
251        area.subtract(area2);
252        g2.setPaint(this.backgroundPaint);
253        g2.fill(area);
254
255        g2.setStroke(this.stroke);
256        g2.setPaint(this.foregroundPaint);
257        g2.draw(window);
258        g2.draw(e);
259    }
260
261    /**
262     * Tests this instance for equality with an arbitrary object.
263     *
264     * @param obj  the object ({@code null} permitted).
265     *
266     * @return A boolean.
267     */
268    @Override
269    public boolean equals(Object obj) {
270        if (obj == this) {
271            return true;
272        }
273        if (!(obj instanceof StandardDialFrame)) {
274            return false;
275        }
276        StandardDialFrame that = (StandardDialFrame) obj;
277        if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) {
278            return false;
279        }
280        if (!PaintUtils.equal(this.foregroundPaint, that.foregroundPaint)) {
281            return false;
282        }
283        if (this.radius != that.radius) {
284            return false;
285        }
286        if (!this.stroke.equals(that.stroke)) {
287            return false;
288        }
289        return super.equals(obj);
290    }
291
292    /**
293     * Returns a hash code for this instance.
294     *
295     * @return The hash code.
296     */
297    @Override
298    public int hashCode() {
299        int result = 193;
300        long temp = Double.doubleToLongBits(this.radius);
301        result = 37 * result + (int) (temp ^ (temp >>> 32));
302        result = 37 * result + HashUtils.hashCodeForPaint(
303                this.backgroundPaint);
304        result = 37 * result + HashUtils.hashCodeForPaint(
305                this.foregroundPaint);
306        result = 37 * result + this.stroke.hashCode();
307        return result;
308    }
309
310    /**
311     * Returns a clone of this instance.
312     *
313     * @return A clone.
314     *
315     * @throws CloneNotSupportedException if any of the frame's attributes
316     *     cannot be cloned.
317     */
318    @Override
319    public Object clone() throws CloneNotSupportedException {
320        return super.clone();
321    }
322
323    /**
324     * Provides serialization support.
325     *
326     * @param stream  the output stream.
327     *
328     * @throws IOException  if there is an I/O error.
329     */
330    private void writeObject(ObjectOutputStream stream) throws IOException {
331        stream.defaultWriteObject();
332        SerialUtils.writePaint(this.backgroundPaint, stream);
333        SerialUtils.writePaint(this.foregroundPaint, stream);
334        SerialUtils.writeStroke(this.stroke, stream);
335    }
336
337    /**
338     * Provides serialization support.
339     *
340     * @param stream  the input stream.
341     *
342     * @throws IOException  if there is an I/O error.
343     * @throws ClassNotFoundException  if there is a classpath problem.
344     */
345    private void readObject(ObjectInputStream stream)
346            throws IOException, ClassNotFoundException {
347        stream.defaultReadObject();
348        this.backgroundPaint = SerialUtils.readPaint(stream);
349        this.foregroundPaint = SerialUtils.readPaint(stream);
350        this.stroke = SerialUtils.readStroke(stream);
351    }
352
353}