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 * DialPointer.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 * 17-Oct-2007 : Added equals() overrides (DG);
039 * 24-Oct-2007 : Implemented PublicCloneable, changed default radius,
040 * and added argument checks (DG);
041 * 23-Nov-2007 : Added fillPaint and outlinePaint attributes to
042 * DialPointer.Pointer (DG);
043 *
044 */
045
046 package org.jfree.chart.plot.dial;
047
048 import java.awt.BasicStroke;
049 import java.awt.Color;
050 import java.awt.Graphics2D;
051 import java.awt.Paint;
052 import java.awt.Stroke;
053 import java.awt.geom.Arc2D;
054 import java.awt.geom.GeneralPath;
055 import java.awt.geom.Line2D;
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 base class for the pointer in a {@link DialPlot}.
070 *
071 * @since 1.0.7
072 */
073 public abstract class DialPointer extends AbstractDialLayer
074 implements DialLayer, Cloneable, PublicCloneable, Serializable {
075
076 /** The needle radius. */
077 double radius;
078
079 /**
080 * The dataset index for the needle.
081 */
082 int datasetIndex;
083
084 /**
085 * Creates a new <code>DialPointer</code> instance.
086 */
087 protected DialPointer() {
088 this(0);
089 }
090
091 /**
092 * Creates a new pointer for the specified dataset.
093 *
094 * @param datasetIndex the dataset index.
095 */
096 protected DialPointer(int datasetIndex) {
097 this.radius = 0.9;
098 this.datasetIndex = datasetIndex;
099 }
100
101 /**
102 * Returns the dataset index that the pointer maps to.
103 *
104 * @return The dataset index.
105 *
106 * @see #getDatasetIndex()
107 */
108 public int getDatasetIndex() {
109 return this.datasetIndex;
110 }
111
112 /**
113 * Sets the dataset index for the pointer and sends a
114 * {@link DialLayerChangeEvent} to all registered listeners.
115 *
116 * @param index the index.
117 *
118 * @see #getDatasetIndex()
119 */
120 public void setDatasetIndex(int index) {
121 this.datasetIndex = index;
122 notifyListeners(new DialLayerChangeEvent(this));
123 }
124
125 /**
126 * Returns the radius of the pointer, as a percentage of the dial's
127 * framing rectangle.
128 *
129 * @return The radius.
130 *
131 * @see #setRadius(double)
132 */
133 public double getRadius() {
134 return this.radius;
135 }
136
137 /**
138 * Sets the radius of the pointer and sends a
139 * {@link DialLayerChangeEvent} to all registered listeners.
140 *
141 * @param radius the radius.
142 *
143 * @see #getRadius()
144 */
145 public void setRadius(double radius) {
146 this.radius = radius;
147 notifyListeners(new DialLayerChangeEvent(this));
148 }
149
150 /**
151 * Returns <code>true</code> to indicate that this layer should be
152 * clipped within the dial window.
153 *
154 * @return <code>true</code>.
155 */
156 public boolean isClippedToWindow() {
157 return true;
158 }
159
160 /**
161 * Checks this instance for equality with an arbitrary object.
162 *
163 * @param obj the object (<code>null</code> not permitted).
164 *
165 * @return A boolean.
166 */
167 public boolean equals(Object obj) {
168 if (obj == this) {
169 return true;
170 }
171 if (!(obj instanceof DialPointer)) {
172 return false;
173 }
174 DialPointer that = (DialPointer) obj;
175 if (this.datasetIndex != that.datasetIndex) {
176 return false;
177 }
178 if (this.radius != that.radius) {
179 return false;
180 }
181 return super.equals(obj);
182 }
183
184 /**
185 * Returns a hash code.
186 *
187 * @return A hash code.
188 */
189 public int hashCode() {
190 int result = 23;
191 result = HashUtilities.hashCode(result, this.radius);
192 return result;
193 }
194
195 /**
196 * Returns a clone of the pointer.
197 *
198 * @return a clone.
199 *
200 * @throws CloneNotSupportedException if one of the attributes cannot
201 * be cloned.
202 */
203 public Object clone() throws CloneNotSupportedException {
204 return super.clone();
205 }
206
207 /**
208 * A dial pointer that draws a thin line (like a pin).
209 */
210 public static class Pin extends DialPointer {
211
212 /** For serialization. */
213 static final long serialVersionUID = -8445860485367689750L;
214
215 /** The paint. */
216 private transient Paint paint;
217
218 /** The stroke. */
219 private transient Stroke stroke;
220
221 /**
222 * Creates a new instance.
223 */
224 public Pin() {
225 this(0);
226 }
227
228 /**
229 * Creates a new instance.
230 *
231 * @param datasetIndex the dataset index.
232 */
233 public Pin(int datasetIndex) {
234 super(datasetIndex);
235 this.paint = Color.red;
236 this.stroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND,
237 BasicStroke.JOIN_BEVEL);
238 }
239
240 /**
241 * Returns the paint.
242 *
243 * @return The paint (never <code>null</code>).
244 *
245 * @see #setPaint(Paint)
246 */
247 public Paint getPaint() {
248 return this.paint;
249 }
250
251 /**
252 * Sets the paint and sends a {@link DialLayerChangeEvent} to all
253 * registered listeners.
254 *
255 * @param paint the paint (<code>null</code> not permitted).
256 *
257 * @see #getPaint()
258 */
259 public void setPaint(Paint paint) {
260 if (paint == null) {
261 throw new IllegalArgumentException("Null 'paint' argument.");
262 }
263 this.paint = paint;
264 notifyListeners(new DialLayerChangeEvent(this));
265 }
266
267 /**
268 * Returns the stroke.
269 *
270 * @return The stroke (never <code>null</code>).
271 *
272 * @see #setStroke(Stroke)
273 */
274 public Stroke getStroke() {
275 return this.stroke;
276 }
277
278 /**
279 * Sets the stroke and sends a {@link DialLayerChangeEvent} to all
280 * registered listeners.
281 *
282 * @param stroke the stroke (<code>null</code> not permitted).
283 *
284 * @see #getStroke()
285 */
286 public void setStroke(Stroke stroke) {
287 if (stroke == null) {
288 throw new IllegalArgumentException("Null 'stroke' argument.");
289 }
290 this.stroke = stroke;
291 notifyListeners(new DialLayerChangeEvent(this));
292 }
293
294 /**
295 * Draws the pointer.
296 *
297 * @param g2 the graphics target.
298 * @param plot the plot.
299 * @param frame the dial's reference frame.
300 * @param view the dial's view.
301 */
302 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
303 Rectangle2D view) {
304
305 g2.setPaint(this.paint);
306 g2.setStroke(this.stroke);
307 Rectangle2D arcRect = DialPlot.rectangleByRadius(frame,
308 this.radius, this.radius);
309
310 double value = plot.getValue(this.datasetIndex);
311 DialScale scale = plot.getScaleForDataset(this.datasetIndex);
312 double angle = scale.valueToAngle(value);
313
314 Arc2D arc = new Arc2D.Double(arcRect, angle, 0, Arc2D.OPEN);
315 Point2D pt = arc.getEndPoint();
316
317 Line2D line = new Line2D.Double(frame.getCenterX(),
318 frame.getCenterY(), pt.getX(), pt.getY());
319 g2.draw(line);
320 }
321
322 /**
323 * Tests this pointer for equality with an arbitrary object.
324 *
325 * @param obj the object (<code>null</code> permitted).
326 *
327 * @return A boolean.
328 */
329 public boolean equals(Object obj) {
330 if (obj == this) {
331 return true;
332 }
333 if (!(obj instanceof DialPointer.Pin)) {
334 return false;
335 }
336 DialPointer.Pin that = (DialPointer.Pin) obj;
337 if (!PaintUtilities.equal(this.paint, that.paint)) {
338 return false;
339 }
340 if (!this.stroke.equals(that.stroke)) {
341 return false;
342 }
343 return super.equals(obj);
344 }
345
346 /**
347 * Returns a hash code for this instance.
348 *
349 * @return A hash code.
350 */
351 public int hashCode() {
352 int result = super.hashCode();
353 result = HashUtilities.hashCode(result, this.paint);
354 result = HashUtilities.hashCode(result, this.stroke);
355 return result;
356 }
357
358 /**
359 * Provides serialization support.
360 *
361 * @param stream the output stream.
362 *
363 * @throws IOException if there is an I/O error.
364 */
365 private void writeObject(ObjectOutputStream stream) throws IOException {
366 stream.defaultWriteObject();
367 SerialUtilities.writePaint(this.paint, stream);
368 SerialUtilities.writeStroke(this.stroke, stream);
369 }
370
371 /**
372 * Provides serialization support.
373 *
374 * @param stream the input stream.
375 *
376 * @throws IOException if there is an I/O error.
377 * @throws ClassNotFoundException if there is a classpath problem.
378 */
379 private void readObject(ObjectInputStream stream)
380 throws IOException, ClassNotFoundException {
381 stream.defaultReadObject();
382 this.paint = SerialUtilities.readPaint(stream);
383 this.stroke = SerialUtilities.readStroke(stream);
384 }
385
386 }
387
388 /**
389 * A dial pointer.
390 */
391 public static class Pointer extends DialPointer {
392
393 /** For serialization. */
394 static final long serialVersionUID = -4180500011963176960L;
395
396 /**
397 * The radius that defines the width of the pointer at the base.
398 */
399 private double widthRadius;
400
401 /**
402 * The fill paint.
403 *
404 * @since 1.0.8
405 */
406 private transient Paint fillPaint;
407
408 /**
409 * The outline paint.
410 *
411 * @since 1.0.8
412 */
413 private transient Paint outlinePaint;
414
415 /**
416 * Creates a new instance.
417 */
418 public Pointer() {
419 this(0);
420 }
421
422 /**
423 * Creates a new instance.
424 *
425 * @param datasetIndex the dataset index.
426 */
427 public Pointer(int datasetIndex) {
428 super(datasetIndex);
429 this.widthRadius = 0.05;
430 this.fillPaint = Color.gray;
431 this.outlinePaint = Color.black;
432 }
433
434 /**
435 * Returns the width radius.
436 *
437 * @return The width radius.
438 *
439 * @see #setWidthRadius(double)
440 */
441 public double getWidthRadius() {
442 return this.widthRadius;
443 }
444
445 /**
446 * Sets the width radius and sends a {@link DialLayerChangeEvent} to
447 * all registered listeners.
448 *
449 * @param radius the radius
450 *
451 * @see #getWidthRadius()
452 */
453 public void setWidthRadius(double radius) {
454 this.widthRadius = radius;
455 notifyListeners(new DialLayerChangeEvent(this));
456 }
457
458 /**
459 * Returns the fill paint.
460 *
461 * @return The paint (never <code>null</code>).
462 *
463 * @see #setFillPaint(Paint)
464 *
465 * @since 1.0.8
466 */
467 public Paint getFillPaint() {
468 return this.fillPaint;
469 }
470
471 /**
472 * Sets the fill paint and sends a {@link DialLayerChangeEvent} to all
473 * registered listeners.
474 *
475 * @param paint the paint (<code>null</code> not permitted).
476 *
477 * @see #getFillPaint()
478 *
479 * @since 1.0.8
480 */
481 public void setFillPaint(Paint paint) {
482 if (paint == null) {
483 throw new IllegalArgumentException("Null 'paint' argument.");
484 }
485 this.fillPaint = paint;
486 notifyListeners(new DialLayerChangeEvent(this));
487 }
488
489 /**
490 * Returns the outline paint.
491 *
492 * @return The paint (never <code>null</code>).
493 *
494 * @see #setOutlinePaint(Paint)
495 *
496 * @since 1.0.8
497 */
498 public Paint getOutlinePaint() {
499 return this.outlinePaint;
500 }
501
502 /**
503 * Sets the outline paint and sends a {@link DialLayerChangeEvent} to
504 * all registered listeners.
505 *
506 * @param paint the paint (<code>null</code> not permitted).
507 *
508 * @see #getOutlinePaint()
509 *
510 * @since 1.0.8
511 */
512 public void setOutlinePaint(Paint paint) {
513 if (paint == null) {
514 throw new IllegalArgumentException("Null 'paint' argument.");
515 }
516 this.outlinePaint = paint;
517 notifyListeners(new DialLayerChangeEvent(this));
518 }
519
520 /**
521 * Draws the pointer.
522 *
523 * @param g2 the graphics target.
524 * @param plot the plot.
525 * @param frame the dial's reference frame.
526 * @param view the dial's view.
527 */
528 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
529 Rectangle2D view) {
530
531 g2.setPaint(Color.blue);
532 g2.setStroke(new BasicStroke(1.0f));
533 Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame,
534 this.radius, this.radius);
535 Rectangle2D widthRect = DialPlot.rectangleByRadius(frame,
536 this.widthRadius, this.widthRadius);
537 double value = plot.getValue(this.datasetIndex);
538 DialScale scale = plot.getScaleForDataset(this.datasetIndex);
539 double angle = scale.valueToAngle(value);
540
541 Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN);
542 Point2D pt1 = arc1.getEndPoint();
543 Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0,
544 Arc2D.OPEN);
545 Point2D pt2 = arc2.getStartPoint();
546 Point2D pt3 = arc2.getEndPoint();
547 Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0,
548 Arc2D.OPEN);
549 Point2D pt4 = arc3.getStartPoint();
550
551 GeneralPath gp = new GeneralPath();
552 gp.moveTo((float) pt1.getX(), (float) pt1.getY());
553 gp.lineTo((float) pt2.getX(), (float) pt2.getY());
554 gp.lineTo((float) pt4.getX(), (float) pt4.getY());
555 gp.lineTo((float) pt3.getX(), (float) pt3.getY());
556 gp.closePath();
557 g2.setPaint(this.fillPaint);
558 g2.fill(gp);
559
560 g2.setPaint(this.outlinePaint);
561 Line2D line = new Line2D.Double(frame.getCenterX(),
562 frame.getCenterY(), pt1.getX(), pt1.getY());
563 g2.draw(line);
564
565 line.setLine(pt2, pt3);
566 g2.draw(line);
567
568 line.setLine(pt3, pt1);
569 g2.draw(line);
570
571 line.setLine(pt2, pt1);
572 g2.draw(line);
573
574 line.setLine(pt2, pt4);
575 g2.draw(line);
576
577 line.setLine(pt3, pt4);
578 g2.draw(line);
579 }
580
581 /**
582 * Tests this pointer for equality with an arbitrary object.
583 *
584 * @param obj the object (<code>null</code> permitted).
585 *
586 * @return A boolean.
587 */
588 public boolean equals(Object obj) {
589 if (obj == this) {
590 return true;
591 }
592 if (!(obj instanceof DialPointer.Pointer)) {
593 return false;
594 }
595 DialPointer.Pointer that = (DialPointer.Pointer) obj;
596
597 if (this.widthRadius != that.widthRadius) {
598 return false;
599 }
600 if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
601 return false;
602 }
603 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
604 return false;
605 }
606 return super.equals(obj);
607 }
608
609 /**
610 * Returns a hash code for this instance.
611 *
612 * @return A hash code.
613 */
614 public int hashCode() {
615 int result = super.hashCode();
616 result = HashUtilities.hashCode(result, this.widthRadius);
617 result = HashUtilities.hashCode(result, this.fillPaint);
618 result = HashUtilities.hashCode(result, this.outlinePaint);
619 return result;
620 }
621
622 /**
623 * Provides serialization support.
624 *
625 * @param stream the output stream.
626 *
627 * @throws IOException if there is an I/O error.
628 */
629 private void writeObject(ObjectOutputStream stream) throws IOException {
630 stream.defaultWriteObject();
631 SerialUtilities.writePaint(this.fillPaint, stream);
632 SerialUtilities.writePaint(this.outlinePaint, stream);
633 }
634
635 /**
636 * Provides serialization support.
637 *
638 * @param stream the input stream.
639 *
640 * @throws IOException if there is an I/O error.
641 * @throws ClassNotFoundException if there is a classpath problem.
642 */
643 private void readObject(ObjectInputStream stream)
644 throws IOException, ClassNotFoundException {
645 stream.defaultReadObject();
646 this.fillPaint = SerialUtilities.readPaint(stream);
647 this.outlinePaint = SerialUtilities.readPaint(stream);
648 }
649
650 }
651
652 }