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 * ArcDialFrame.java
029 * -----------------
030 * (C) Copyright 2006-2008, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * Changes
036 * -------
037 * 03-Nov-2006 : Version 1 (DG);
038 * 08-Mar-2007 : Fix in hashCode() (DG);
039 * 17-Oct-2007 : Updated equals() (DG);
040 * 24-Oct-2007 : Added argument checks and API docs, and renamed
041 * StandardDialFrame --> ArcDialFrame (DG);
042 *
043 */
044
045 package org.jfree.chart.plot.dial;
046
047 import java.awt.BasicStroke;
048 import java.awt.Color;
049 import java.awt.Graphics2D;
050 import java.awt.Paint;
051 import java.awt.Shape;
052 import java.awt.Stroke;
053 import java.awt.geom.Arc2D;
054 import java.awt.geom.Area;
055 import java.awt.geom.GeneralPath;
056 import java.awt.geom.Point2D;
057 import java.awt.geom.Rectangle2D;
058 import java.io.IOException;
059 import java.io.ObjectInputStream;
060 import java.io.ObjectOutputStream;
061 import java.io.Serializable;
062
063 import org.jfree.chart.HashUtilities;
064 import org.jfree.io.SerialUtilities;
065 import org.jfree.util.PaintUtilities;
066 import org.jfree.util.PublicCloneable;
067
068 /**
069 * A standard frame for the {@link DialPlot} class.
070 *
071 * @since 1.0.7
072 */
073 public class ArcDialFrame extends AbstractDialLayer implements DialFrame,
074 Cloneable, PublicCloneable, Serializable {
075
076 /** For serialization. */
077 static final long serialVersionUID = -4089176959553523499L;
078
079 /**
080 * The color used for the front of the panel. This field is transient
081 * because it requires special handling for serialization.
082 */
083 private transient Paint backgroundPaint;
084
085 /**
086 * The color used for the border around the window. This field is transient
087 * because it requires special handling for serialization.
088 */
089 private transient Paint foregroundPaint;
090
091 /**
092 * The stroke for drawing the frame outline. This field is transient
093 * because it requires special handling for serialization.
094 */
095 private transient Stroke stroke;
096
097 /**
098 * The start angle.
099 */
100 private double startAngle;
101
102 /**
103 * The end angle.
104 */
105 private double extent;
106
107 /** The inner radius, relative to the framing rectangle. */
108 private double innerRadius;
109
110 /** The outer radius, relative to the framing rectangle. */
111 private double outerRadius;
112
113 /**
114 * Creates a new instance of <code>ArcDialFrame</code> that spans
115 * 180 degrees.
116 */
117 public ArcDialFrame() {
118 this(0, 180);
119 }
120
121 /**
122 * Creates a new instance of <code>ArcDialFrame</code> that spans
123 * the arc specified.
124 *
125 * @param startAngle the startAngle (in degrees).
126 * @param extent the extent of the arc (in degrees, counter-clockwise).
127 */
128 public ArcDialFrame(double startAngle, double extent) {
129 this.backgroundPaint = Color.gray;
130 this.foregroundPaint = new Color(100, 100, 150);
131 this.stroke = new BasicStroke(2.0f);
132 this.innerRadius = 0.25;
133 this.outerRadius = 0.75;
134 this.startAngle = startAngle;
135 this.extent = extent;
136 }
137
138 /**
139 * Returns the background paint (never <code>null</code>).
140 *
141 * @return The background paint.
142 *
143 * @see #setBackgroundPaint(Paint)
144 */
145 public Paint getBackgroundPaint() {
146 return this.backgroundPaint;
147 }
148
149 /**
150 * Sets the background paint and sends a {@link DialLayerChangeEvent} to
151 * all registered listeners.
152 *
153 * @param paint the paint (<code>null</code> not permitted).
154 *
155 * @see #getBackgroundPaint()
156 */
157 public void setBackgroundPaint(Paint paint) {
158 if (paint == null) {
159 throw new IllegalArgumentException("Null 'paint' argument.");
160 }
161 this.backgroundPaint = paint;
162 notifyListeners(new DialLayerChangeEvent(this));
163 }
164
165 /**
166 * Returns the foreground paint.
167 *
168 * @return The foreground paint (never <code>null</code>).
169 *
170 * @see #setForegroundPaint(Paint)
171 */
172 public Paint getForegroundPaint() {
173 return this.foregroundPaint;
174 }
175
176 /**
177 * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to
178 * all registered listeners.
179 *
180 * @param paint the paint (<code>null</code> not permitted).
181 *
182 * @see #getForegroundPaint()
183 */
184 public void setForegroundPaint(Paint paint) {
185 if (paint == null) {
186 throw new IllegalArgumentException("Null 'paint' argument.");
187 }
188 this.foregroundPaint = paint;
189 notifyListeners(new DialLayerChangeEvent(this));
190 }
191
192 /**
193 * Returns the stroke.
194 *
195 * @return The stroke (never <code>null</code>).
196 *
197 * @see #setStroke(Stroke)
198 */
199 public Stroke getStroke() {
200 return this.stroke;
201 }
202
203 /**
204 * Sets the stroke and sends a {@link DialLayerChangeEvent} to
205 * all registered listeners.
206 *
207 * @param stroke the stroke (<code>null</code> not permitted).
208 *
209 * @see #getStroke()
210 */
211 public void setStroke(Stroke stroke) {
212 if (stroke == null) {
213 throw new IllegalArgumentException("Null 'stroke' argument.");
214 }
215 this.stroke = stroke;
216 notifyListeners(new DialLayerChangeEvent(this));
217 }
218
219 /**
220 * Returns the inner radius, relative to the framing rectangle.
221 *
222 * @return The inner radius.
223 *
224 * @see #setInnerRadius(double)
225 */
226 public double getInnerRadius() {
227 return this.innerRadius;
228 }
229
230 /**
231 * Sets the inner radius and sends a {@link DialLayerChangeEvent} to
232 * all registered listeners.
233 *
234 * @param radius the inner radius.
235 *
236 * @see #getInnerRadius()
237 */
238 public void setInnerRadius(double radius) {
239 if (radius < 0.0) {
240 throw new IllegalArgumentException("Negative 'radius' argument.");
241 }
242 this.innerRadius = radius;
243 notifyListeners(new DialLayerChangeEvent(this));
244 }
245
246 /**
247 * Returns the outer radius, relative to the framing rectangle.
248 *
249 * @return The outer radius.
250 *
251 * @see #setOuterRadius(double)
252 */
253 public double getOuterRadius() {
254 return this.outerRadius;
255 }
256
257 /**
258 * Sets the outer radius and sends a {@link DialLayerChangeEvent} to
259 * all registered listeners.
260 *
261 * @param radius the outer radius.
262 *
263 * @see #getOuterRadius()
264 */
265 public void setOuterRadius(double radius) {
266 if (radius < 0.0) {
267 throw new IllegalArgumentException("Negative 'radius' argument.");
268 }
269 this.outerRadius = radius;
270 notifyListeners(new DialLayerChangeEvent(this));
271 }
272
273 /**
274 * Returns the start angle.
275 *
276 * @return The start angle.
277 *
278 * @see #setStartAngle(double)
279 */
280 public double getStartAngle() {
281 return this.startAngle;
282 }
283
284 /**
285 * Sets the start angle and sends a {@link DialLayerChangeEvent} to
286 * all registered listeners.
287 *
288 * @param angle the angle.
289 *
290 * @see #getStartAngle()
291 */
292 public void setStartAngle(double angle) {
293 this.startAngle = angle;
294 notifyListeners(new DialLayerChangeEvent(this));
295 }
296
297 /**
298 * Returns the extent.
299 *
300 * @return The extent.
301 *
302 * @see #setExtent(double)
303 */
304 public double getExtent() {
305 return this.extent;
306 }
307
308 /**
309 * Sets the extent and sends a {@link DialLayerChangeEvent} to
310 * all registered listeners.
311 *
312 * @param extent the extent.
313 *
314 * @see #getExtent()
315 */
316 public void setExtent(double extent) {
317 this.extent = extent;
318 notifyListeners(new DialLayerChangeEvent(this));
319 }
320
321 /**
322 * Returns the shape for the window for this dial. Some dial layers will
323 * request that their drawing be clipped within this window.
324 *
325 * @param frame the reference frame (<code>null</code> not permitted).
326 *
327 * @return The shape of the dial's window.
328 */
329 public Shape getWindow(Rectangle2D frame) {
330
331 Rectangle2D innerFrame = DialPlot.rectangleByRadius(frame,
332 this.innerRadius, this.innerRadius);
333 Rectangle2D outerFrame = DialPlot.rectangleByRadius(frame,
334 this.outerRadius, this.outerRadius);
335 Arc2D inner = new Arc2D.Double(innerFrame, this.startAngle,
336 this.extent, Arc2D.OPEN);
337 Arc2D outer = new Arc2D.Double(outerFrame, this.startAngle
338 + this.extent, -this.extent, Arc2D.OPEN);
339 GeneralPath p = new GeneralPath();
340 Point2D point1 = inner.getStartPoint();
341 p.moveTo((float) point1.getX(), (float) point1.getY());
342 p.append(inner, true);
343 p.append(outer, true);
344 p.closePath();
345 return p;
346
347 }
348
349 /**
350 * Returns the outer window.
351 *
352 * @param frame the frame.
353 *
354 * @return The outer window.
355 */
356 protected Shape getOuterWindow(Rectangle2D frame) {
357 double radiusMargin = 0.02;
358 double angleMargin = 1.5;
359 Rectangle2D innerFrame = DialPlot.rectangleByRadius(frame,
360 this.innerRadius - radiusMargin, this.innerRadius
361 - radiusMargin);
362 Rectangle2D outerFrame = DialPlot.rectangleByRadius(frame,
363 this.outerRadius + radiusMargin, this.outerRadius
364 + radiusMargin);
365 Arc2D inner = new Arc2D.Double(innerFrame, this.startAngle
366 - angleMargin, this.extent + 2 * angleMargin, Arc2D.OPEN);
367 Arc2D outer = new Arc2D.Double(outerFrame, this.startAngle
368 + angleMargin + this.extent, -this.extent - 2 * angleMargin,
369 Arc2D.OPEN);
370 GeneralPath p = new GeneralPath();
371 Point2D point1 = inner.getStartPoint();
372 p.moveTo((float) point1.getX(), (float) point1.getY());
373 p.append(inner, true);
374 p.append(outer, true);
375 p.closePath();
376 return p;
377 }
378
379 /**
380 * Draws the frame.
381 *
382 * @param g2 the graphics target.
383 * @param plot the plot.
384 * @param frame the dial's reference frame.
385 * @param view the dial's view rectangle.
386 */
387 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
388 Rectangle2D view) {
389
390 Shape window = getWindow(frame);
391 Shape outerWindow = getOuterWindow(frame);
392
393 Area area1 = new Area(outerWindow);
394 Area area2 = new Area(window);
395 area1.subtract(area2);
396 g2.setPaint(Color.lightGray);
397 g2.fill(area1);
398
399 g2.setStroke(this.stroke);
400 g2.setPaint(this.foregroundPaint);
401 g2.draw(window);
402 g2.draw(outerWindow);
403
404 }
405
406 /**
407 * Returns <code>false</code> to indicate that this dial layer is not
408 * clipped to the dial window.
409 *
410 * @return <code>false</code>.
411 */
412 public boolean isClippedToWindow() {
413 return false;
414 }
415
416 /**
417 * Tests this instance for equality with an arbitrary object.
418 *
419 * @param obj the object (<code>null</code> permitted).
420 *
421 * @return A boolean.
422 */
423 public boolean equals(Object obj) {
424 if (obj == this) {
425 return true;
426 }
427 if (!(obj instanceof ArcDialFrame)) {
428 return false;
429 }
430 ArcDialFrame that = (ArcDialFrame) obj;
431 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
432 return false;
433 }
434 if (!PaintUtilities.equal(this.foregroundPaint, that.foregroundPaint)) {
435 return false;
436 }
437 if (this.startAngle != that.startAngle) {
438 return false;
439 }
440 if (this.extent != that.extent) {
441 return false;
442 }
443 if (this.innerRadius != that.innerRadius) {
444 return false;
445 }
446 if (this.outerRadius != that.outerRadius) {
447 return false;
448 }
449 if (!this.stroke.equals(that.stroke)) {
450 return false;
451 }
452 return super.equals(obj);
453 }
454
455 /**
456 * Returns a hash code for this instance.
457 *
458 * @return The hash code.
459 */
460 public int hashCode() {
461 int result = 193;
462 long temp = Double.doubleToLongBits(this.startAngle);
463 result = 37 * result + (int) (temp ^ (temp >>> 32));
464 temp = Double.doubleToLongBits(this.extent);
465 result = 37 * result + (int) (temp ^ (temp >>> 32));
466 temp = Double.doubleToLongBits(this.innerRadius);
467 result = 37 * result + (int) (temp ^ (temp >>> 32));
468 temp = Double.doubleToLongBits(this.outerRadius);
469 result = 37 * result + (int) (temp ^ (temp >>> 32));
470 result = 37 * result + HashUtilities.hashCodeForPaint(
471 this.backgroundPaint);
472 result = 37 * result + HashUtilities.hashCodeForPaint(
473 this.foregroundPaint);
474 result = 37 * result + this.stroke.hashCode();
475 return result;
476 }
477
478 /**
479 * Returns a clone of this instance.
480 *
481 * @return A clone.
482 *
483 * @throws CloneNotSupportedException if any attribute of this instance
484 * cannot be cloned.
485 */
486 public Object clone() throws CloneNotSupportedException {
487 return super.clone();
488 }
489
490 /**
491 * Provides serialization support.
492 *
493 * @param stream the output stream.
494 *
495 * @throws IOException if there is an I/O error.
496 */
497 private void writeObject(ObjectOutputStream stream) throws IOException {
498 stream.defaultWriteObject();
499 SerialUtilities.writePaint(this.backgroundPaint, stream);
500 SerialUtilities.writePaint(this.foregroundPaint, stream);
501 SerialUtilities.writeStroke(this.stroke, stream);
502 }
503
504 /**
505 * Provides serialization support.
506 *
507 * @param stream the input stream.
508 *
509 * @throws IOException if there is an I/O error.
510 * @throws ClassNotFoundException if there is a classpath problem.
511 */
512 private void readObject(ObjectInputStream stream)
513 throws IOException, ClassNotFoundException {
514 stream.defaultReadObject();
515 this.backgroundPaint = SerialUtilities.readPaint(stream);
516 this.foregroundPaint = SerialUtilities.readPaint(stream);
517 this.stroke = SerialUtilities.readStroke(stream);
518 }
519
520 }