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 * StandardDialRange.java
029 * ----------------------
030 * (C) Copyright 2006, 2007, 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 : Removed increment attribute (DG);
040 * 24-Oct-2007 : Added scaleIndex (DG);
041 *
042 */
043
044 package org.jfree.chart.plot.dial;
045
046 import java.awt.BasicStroke;
047 import java.awt.Color;
048 import java.awt.Graphics2D;
049 import java.awt.Paint;
050 import java.awt.geom.Arc2D;
051 import java.awt.geom.Rectangle2D;
052 import java.io.IOException;
053 import java.io.ObjectInputStream;
054 import java.io.ObjectOutputStream;
055 import java.io.Serializable;
056
057 import org.jfree.chart.HashUtilities;
058 import org.jfree.io.SerialUtilities;
059 import org.jfree.util.PaintUtilities;
060 import org.jfree.util.PublicCloneable;
061
062 /**
063 * A layer that draws a range highlight on a dial plot.
064 *
065 * @since 1.0.7
066 */
067 public class StandardDialRange extends AbstractDialLayer implements DialLayer,
068 Cloneable, PublicCloneable, Serializable {
069
070 /** For serialization. */
071 static final long serialVersionUID = 345515648249364904L;
072
073 /** The scale index. */
074 private int scaleIndex;
075
076 /** The minimum data value for the scale. */
077 private double lowerBound;
078
079 /** The maximum data value for the scale. */
080 private double upperBound;
081
082 /**
083 * The paint used to draw the range highlight. This field is transient
084 * because it requires special handling for serialization.
085 */
086 private transient Paint paint;
087
088 /**
089 * The factor (in the range 0.0 to 1.0) that determines the inside limit
090 * of the range highlight.
091 */
092 private double innerRadius;
093
094 /**
095 * The factor (in the range 0.0 to 1.0) that determines the outside limit
096 * of the range highlight.
097 */
098 private double outerRadius;
099
100 /**
101 * Creates a new instance of <code>StandardDialRange</code>.
102 */
103 public StandardDialRange() {
104 this(0.0, 100.0, Color.white);
105 }
106
107 /**
108 * Creates a new instance of <code>StandardDialRange</code>.
109 *
110 * @param lower the lower bound.
111 * @param upper the upper bound.
112 * @param paint the paint (<code>null</code> not permitted).
113 */
114 public StandardDialRange(double lower, double upper, Paint paint) {
115 if (paint == null) {
116 throw new IllegalArgumentException("Null 'paint' argument.");
117 }
118 this.scaleIndex = 0;
119 this.lowerBound = lower;
120 this.upperBound = upper;
121 this.innerRadius = 0.48;
122 this.outerRadius = 0.52;
123 this.paint = paint;
124 }
125
126 /**
127 * Returns the scale index.
128 *
129 * @return The scale index.
130 *
131 * @see #setScaleIndex(int)
132 */
133 public int getScaleIndex() {
134 return this.scaleIndex;
135 }
136
137 /**
138 * Sets the scale index and sends a {@link DialLayerChangeEvent} to all
139 * registered listeners.
140 *
141 * @param index the scale index.
142 *
143 * @see #getScaleIndex()
144 */
145 public void setScaleIndex(int index) {
146 this.scaleIndex = index;
147 notifyListeners(new DialLayerChangeEvent(this));
148 }
149
150 /**
151 * Returns the lower bound (a data value) of the dial range.
152 *
153 * @return The lower bound of the dial range.
154 *
155 * @see #setLowerBound(double)
156 */
157 public double getLowerBound() {
158 return this.lowerBound;
159 }
160
161 /**
162 * Sets the lower bound of the dial range and sends a
163 * {@link DialLayerChangeEvent} to all registered listeners.
164 *
165 * @param bound the lower bound.
166 *
167 * @see #getLowerBound()
168 */
169 public void setLowerBound(double bound) {
170 if (bound >= this.upperBound) {
171 throw new IllegalArgumentException(
172 "Lower bound must be less than upper bound.");
173 }
174 this.lowerBound = bound;
175 notifyListeners(new DialLayerChangeEvent(this));
176 }
177
178 /**
179 * Returns the upper bound of the dial range.
180 *
181 * @return The upper bound.
182 *
183 * @see #setUpperBound(double)
184 */
185 public double getUpperBound() {
186 return this.upperBound;
187 }
188
189 /**
190 * Sets the upper bound of the dial range and sends a
191 * {@link DialLayerChangeEvent} to all registered listeners.
192 *
193 * @param bound the upper bound.
194 *
195 * @see #getUpperBound()
196 */
197 public void setUpperBound(double bound) {
198 if (bound <= this.lowerBound) {
199 throw new IllegalArgumentException(
200 "Lower bound must be less than upper bound.");
201 }
202 this.upperBound = bound;
203 notifyListeners(new DialLayerChangeEvent(this));
204 }
205
206 /**
207 * Sets the bounds for the range and sends a {@link DialLayerChangeEvent}
208 * to all registered listeners.
209 *
210 * @param lower the lower bound.
211 * @param upper the upper bound.
212 */
213 public void setBounds(double lower, double upper) {
214 if (lower >= upper) {
215 throw new IllegalArgumentException(
216 "Lower must be less than upper.");
217 }
218 this.lowerBound = lower;
219 this.upperBound = upper;
220 notifyListeners(new DialLayerChangeEvent(this));
221 }
222
223 /**
224 * Returns the paint used to highlight the range.
225 *
226 * @return The paint (never <code>null</code>).
227 *
228 * @see #setPaint(Paint)
229 */
230 public Paint getPaint() {
231 return this.paint;
232 }
233
234 /**
235 * Sets the paint used to highlight the range and sends a
236 * {@link DialLayerChangeEvent} to all registered listeners.
237 *
238 * @param paint the paint (<code>null</code> not permitted).
239 *
240 * @see #getPaint()
241 */
242 public void setPaint(Paint paint) {
243 if (paint == null) {
244 throw new IllegalArgumentException("Null 'paint' argument.");
245 }
246 this.paint = paint;
247 notifyListeners(new DialLayerChangeEvent(this));
248 }
249
250 /**
251 * Returns the inner radius.
252 *
253 * @return The inner radius.
254 *
255 * @see #setInnerRadius(double)
256 */
257 public double getInnerRadius() {
258 return this.innerRadius;
259 }
260
261 /**
262 * Sets the inner radius and sends a {@link DialLayerChangeEvent} to all
263 * registered listeners.
264 *
265 * @param radius the radius.
266 *
267 * @see #getInnerRadius()
268 */
269 public void setInnerRadius(double radius) {
270 this.innerRadius = radius;
271 notifyListeners(new DialLayerChangeEvent(this));
272 }
273
274 /**
275 * Returns the outer radius.
276 *
277 * @return The outer radius.
278 *
279 * @see #setOuterRadius(double)
280 */
281 public double getOuterRadius() {
282 return this.outerRadius;
283 }
284
285 /**
286 * Sets the outer radius and sends a {@link DialLayerChangeEvent} to all
287 * registered listeners.
288 *
289 * @param radius the radius.
290 *
291 * @see #getOuterRadius()
292 */
293 public void setOuterRadius(double radius) {
294 this.outerRadius = radius;
295 notifyListeners(new DialLayerChangeEvent(this));
296 }
297
298 /**
299 * Returns <code>true</code> to indicate that this layer should be
300 * clipped within the dial window.
301 *
302 * @return <code>true</code>.
303 */
304 public boolean isClippedToWindow() {
305 return true;
306 }
307
308 /**
309 * Draws the range.
310 *
311 * @param g2 the graphics target.
312 * @param plot the plot.
313 * @param frame the dial's reference frame (in Java2D space).
314 * @param view the dial's view rectangle (in Java2D space).
315 */
316 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
317 Rectangle2D view) {
318
319 Rectangle2D arcRectInner = DialPlot.rectangleByRadius(frame,
320 this.innerRadius, this.innerRadius);
321 Rectangle2D arcRectOuter = DialPlot.rectangleByRadius(frame,
322 this.outerRadius, this.outerRadius);
323
324 DialScale scale = plot.getScale(this.scaleIndex);
325 if (scale == null) {
326 throw new RuntimeException("No scale for scaleIndex = "
327 + this.scaleIndex);
328 }
329 double angleMin = scale.valueToAngle(this.lowerBound);
330 double angleMax = scale.valueToAngle(this.upperBound);
331
332 Arc2D arcInner = new Arc2D.Double(arcRectInner, angleMin,
333 angleMax - angleMin, Arc2D.OPEN);
334 Arc2D arcOuter = new Arc2D.Double(arcRectOuter, angleMax,
335 angleMin - angleMax, Arc2D.OPEN);
336
337 g2.setPaint(this.paint);
338 g2.setStroke(new BasicStroke(2.0f));
339 g2.draw(arcInner);
340 g2.draw(arcOuter);
341 }
342
343 /**
344 * Tests this instance for equality with an arbitrary object.
345 *
346 * @param obj the object (<code>null</code> permitted).
347 *
348 * @return A boolean.
349 */
350 public boolean equals(Object obj) {
351 if (obj == this) {
352 return true;
353 }
354 if (!(obj instanceof StandardDialRange)) {
355 return false;
356 }
357 StandardDialRange that = (StandardDialRange) obj;
358 if (this.scaleIndex != that.scaleIndex) {
359 return false;
360 }
361 if (this.lowerBound != that.lowerBound) {
362 return false;
363 }
364 if (this.upperBound != that.upperBound) {
365 return false;
366 }
367 if (!PaintUtilities.equal(this.paint, that.paint)) {
368 return false;
369 }
370 if (this.innerRadius != that.innerRadius) {
371 return false;
372 }
373 if (this.outerRadius != that.outerRadius) {
374 return false;
375 }
376 return super.equals(obj);
377 }
378
379 /**
380 * Returns a hash code for this instance.
381 *
382 * @return The hash code.
383 */
384 public int hashCode() {
385 int result = 193;
386 long temp = Double.doubleToLongBits(this.lowerBound);
387 result = 37 * result + (int) (temp ^ (temp >>> 32));
388 temp = Double.doubleToLongBits(this.upperBound);
389 result = 37 * result + (int) (temp ^ (temp >>> 32));
390 temp = Double.doubleToLongBits(this.innerRadius);
391 result = 37 * result + (int) (temp ^ (temp >>> 32));
392 temp = Double.doubleToLongBits(this.outerRadius);
393 result = 37 * result + (int) (temp ^ (temp >>> 32));
394 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
395 return result;
396 }
397
398 /**
399 * Returns a clone of this instance.
400 *
401 * @return A clone.
402 *
403 * @throws CloneNotSupportedException if any of the attributes of this
404 * instance cannot be cloned.
405 */
406 public Object clone() throws CloneNotSupportedException {
407 return super.clone();
408 }
409
410 /**
411 * Provides serialization support.
412 *
413 * @param stream the output stream.
414 *
415 * @throws IOException if there is an I/O error.
416 */
417 private void writeObject(ObjectOutputStream stream) throws IOException {
418 stream.defaultWriteObject();
419 SerialUtilities.writePaint(this.paint, stream);
420 }
421
422 /**
423 * Provides serialization support.
424 *
425 * @param stream the input stream.
426 *
427 * @throws IOException if there is an I/O error.
428 * @throws ClassNotFoundException if there is a classpath problem.
429 */
430 private void readObject(ObjectInputStream stream)
431 throws IOException, ClassNotFoundException {
432 stream.defaultReadObject();
433 this.paint = SerialUtilities.readPaint(stream);
434 }
435
436 }