001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2006, 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: * StandardDialScale.java
029: * ----------------------
030: * (C) Copyright 2006, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: StandardDialScale.java,v 1.1.2.3 2006/11/17 11:06:44 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 03-Nov-2006 : Version 1 (DG);
040: * 17-Nov-2006 : Added flags for tick label visibility (DG);
041: *
042: */
043:
044: package org.jfree.experimental.chart.plot.dial;
045:
046: import java.awt.BasicStroke;
047: import java.awt.Color;
048: import java.awt.Font;
049: import java.awt.Graphics2D;
050: import java.awt.Paint;
051: import java.awt.Stroke;
052: import java.awt.geom.Arc2D;
053: import java.awt.geom.Line2D;
054: import java.awt.geom.Point2D;
055: import java.awt.geom.Rectangle2D;
056: import java.io.IOException;
057: import java.io.ObjectInputStream;
058: import java.io.ObjectOutputStream;
059: import java.io.Serializable;
060:
061: import org.jfree.io.SerialUtilities;
062: import org.jfree.text.TextUtilities;
063: import org.jfree.ui.TextAnchor;
064: import org.jfree.util.PaintUtilities;
065: import org.jfree.util.PublicCloneable;
066:
067: /**
068: * A scale for a {@link DialPlot}.
069: */
070: public class StandardDialScale extends AbstractDialLayer implements
071: DialScale, DialLayer, Cloneable, PublicCloneable, Serializable {
072:
073: /** The minimum data value for the scale. */
074: private double lowerBound;
075:
076: /** The maximum data value for the scale. */
077: private double upperBound;
078:
079: /**
080: * The start angle for the scale display, in degrees (using the same
081: * encoding as Arc2D).
082: */
083: private double startAngle;
084:
085: /** The extent of the scale display. */
086: private double extent;
087:
088: /**
089: * The factor (in the range 0.0 to 1.0) that determines the outside limit
090: * of the tick marks.
091: */
092: private double tickRadius;
093:
094: /**
095: * The increment (in data units) between major tick marks.
096: */
097: private double majorTickIncrement;
098:
099: /**
100: * The factor that is subtracted from the tickRadius to determine the
101: * inner point of the major ticks.
102: */
103: private double majorTickLength;
104:
105: /**
106: * The paint to use for major tick marks. This field is transient because
107: * it requires special handling for serialization.
108: */
109: private transient Paint majorTickPaint;
110:
111: /**
112: * The stroke to use for major tick marks. This field is transient because
113: * it requires special handling for serialization.
114: */
115: private transient Stroke majorTickStroke;
116:
117: /**
118: * The number of minor ticks between each major tick.
119: */
120: private int minorTickCount;
121:
122: /**
123: * The factor that is subtracted from the tickRadius to determine the
124: * inner point of the minor ticks.
125: */
126: private double minorTickLength;
127:
128: /**
129: * The tick label offset.
130: */
131: private double tickLabelOffset;
132:
133: /**
134: * The tick label font.
135: */
136: private Font tickLabelFont;
137:
138: /**
139: * A flag that controls whether or not the tick labels are
140: * displayed.
141: */
142: private boolean tickLabelsVisible;
143:
144: /**
145: * A flag that controls whether or not the first tick label is
146: * displayed.
147: */
148: private boolean firstTickLabelVisible;
149:
150: /**
151: * The tick label paint. This field is transient because it requires
152: * special handling for serialization.
153: */
154: private transient Paint tickLabelPaint;
155:
156: /**
157: * Creates a new instance of DialScale.
158: */
159: public StandardDialScale() {
160: this (0.0, 100.0, 175, -170);
161: }
162:
163: /**
164: * Creates a new instance.
165: *
166: * @param lowerBound the lower bound of the scale.
167: * @param upperBound the upper bound of the scale.
168: * @param startAngle the start angle (in degrees, using the same
169: * orientation as Java's <code>Arc2D</code> class).
170: * @param extent the extent (in degrees, counter-clockwise).
171: */
172: public StandardDialScale(double lowerBound, double upperBound,
173: double startAngle, double extent) {
174: this .startAngle = startAngle;
175: this .extent = extent;
176: this .lowerBound = lowerBound;
177: this .upperBound = upperBound;
178: this .majorTickPaint = Color.black;
179: this .majorTickStroke = new BasicStroke(3.0f);
180: this .tickLabelFont = new Font("Dialog", Font.BOLD, 16);
181: this .tickLabelPaint = Color.blue;
182: this .minorTickCount = 4;
183: this .minorTickLength = 0.02;
184: this .tickLabelOffset = 0.10;
185: this .majorTickIncrement = 10.0;
186: this .tickRadius = 0.70;
187: this .tickLabelsVisible = true;
188: this .firstTickLabelVisible = true;
189: }
190:
191: /**
192: * Returns the start angle for the scale (in degrees using the same
193: * orientation as Java's <code>Arc2D</code> class).
194: *
195: * @return The start angle.
196: *
197: * @see #setStartAngle(double)
198: */
199: public double getStartAngle() {
200: return this .startAngle;
201: }
202:
203: /**
204: * Sets the start angle for the scale.
205: *
206: * @param angle the angle.
207: *
208: * @see #getStartAngle()
209: */
210: public void setStartAngle(double angle) {
211: this .startAngle = angle;
212: notifyListeners(new DialLayerChangeEvent(this ));
213: }
214:
215: /**
216: * Returns the extent.
217: *
218: * @return The extent.
219: */
220: public double getExtent() {
221: return this .extent;
222: }
223:
224: /**
225: * Sets the extent.
226: *
227: * @param extent the extent.
228: *
229: * @see #getExtent()
230: */
231: public void setExtent(double extent) {
232: this .extent = extent;
233: notifyListeners(new DialLayerChangeEvent(this ));
234: }
235:
236: /**
237: * Returns the radius (as a percentage of the maximum space available) of
238: * the outer limit of the tick marks.
239: *
240: * @return The tick radius.
241: *
242: * @see #setTickRadius(double)
243: */
244: public double getTickRadius() {
245: return this .tickRadius;
246: }
247:
248: /**
249: * Sets the tick radius.
250: *
251: * @param radius the radius.
252: *
253: * @see #getTickRadius()
254: */
255: public void setTickRadius(double radius) {
256: // TODO: Validate
257: this .tickRadius = radius;
258: notifyListeners(new DialLayerChangeEvent(this ));
259: }
260:
261: /**
262: * Returns the increment (in data units) between major tick labels.
263: *
264: * @return The increment between major tick labels.
265: *
266: * @see #setMajorTickIncrement(double)
267: */
268: public double getMajorTickIncrement() {
269: return this .majorTickIncrement;
270: }
271:
272: /**
273: * Sets the increment (in data units) between major tick labels.
274: *
275: * @param increment the increment.
276: *
277: * @see #getMajorTickIncrement()
278: */
279: public void setMajorTickIncrement(double increment) {
280: // TODO: validation
281: this .majorTickIncrement = increment;
282: notifyListeners(new DialLayerChangeEvent(this ));
283: }
284:
285: /**
286: * Returns the length factor for the major tick marks. The value is
287: * subtracted from the tick radius to determine the inner starting point
288: * for the tick marks.
289: *
290: * @return The length factor.
291: *
292: * @see #setMajorTickLength(double)
293: */
294: public double getMajorTickLength() {
295: return this .majorTickLength;
296: }
297:
298: /**
299: * Sets the length factor for the major tick marks.
300: *
301: * @param length the length.
302: *
303: * @see #getMajorTickLength()
304: */
305: public void setMajorTickLength(double length) {
306: // TODO: validation
307: this .majorTickLength = length;
308: notifyListeners(new DialLayerChangeEvent(this ));
309: }
310:
311: /**
312: * Returns the major tick paint.
313: *
314: * @return The major tick paint (never <code>null</code>).
315: *
316: * @see #setMajorTickPaint(Paint)
317: */
318: public Paint getMajorTickPaint() {
319: return this .majorTickPaint;
320: }
321:
322: /**
323: * Sets the major tick paint.
324: *
325: * @param paint the paint (<code>null</code> not permitted).
326: *
327: * @see #getMajorTickPaint()
328: */
329: public void setMajorTickPaint(Paint paint) {
330: if (paint == null) {
331: throw new IllegalArgumentException("Null 'paint' argument.");
332: }
333: this .majorTickPaint = paint;
334: notifyListeners(new DialLayerChangeEvent(this ));
335: }
336:
337: /**
338: * Returns the stroke used to draw the major tick marks.
339: *
340: * @return The stroke (never <code>null</code>).
341: *
342: * @see #setMajorTickStroke(Stroke)
343: */
344: public Stroke getMajorTickStroke() {
345: return this .majorTickStroke;
346: }
347:
348: /**
349: * Sets the stroke used to draw the major tick marks.
350: *
351: * @param stroke the stroke (<code>null</code> not permitted).
352: *
353: * @see #getMajorTickStroke()
354: */
355: public void setMajorTickStroke(Stroke stroke) {
356: if (stroke == null) {
357: throw new IllegalArgumentException(
358: "Null 'stroke' argument.");
359: }
360: this .majorTickStroke = stroke;
361: notifyListeners(new DialLayerChangeEvent(this ));
362: }
363:
364: /**
365: * Returns the number of minor tick marks between major tick marks.
366: *
367: * @return The number of minor tick marks between major tick marks.
368: *
369: * @see #setMinorTickCount(int)
370: */
371: public int getMinorTickCount() {
372: return this .minorTickCount;
373: }
374:
375: /**
376: * Sets the number of minor tick marks between major tick marks.
377: *
378: * @param count the count.
379: *
380: * @see #getMinorTickCount()
381: */
382: public void setMinorTickCount(int count) {
383: // TODO: validation
384: this .minorTickCount = count;
385: notifyListeners(new DialLayerChangeEvent(this ));
386: }
387:
388: /**
389: * Returns the length factor for the minor tick marks. The value is
390: * subtracted from the tick radius to determine the inner starting point
391: * for the tick marks.
392: *
393: * @return The length factor.
394: *
395: * @see #setMinorTickLength(double)
396: */
397: public double getMinorTickLength() {
398: return this .minorTickLength;
399: }
400:
401: /**
402: * Sets the length factor for the minor tick marks.
403: *
404: * @param length the length.
405: *
406: * @see #getMinorTickLength()
407: */
408: public void setMinorTickLength(double length) {
409: // TODO: validation
410: this .minorTickLength = length;
411: notifyListeners(new DialLayerChangeEvent(this ));
412: }
413:
414: /**
415: * Returns the tick label offset.
416: *
417: * @return The tick label offset.
418: *
419: * @see #setTickLabelOffset(double)
420: */
421: public double getTickLabelOffset() {
422: return this .tickLabelOffset;
423: }
424:
425: /**
426: * Sets the tick label offset.
427: *
428: * @param offset the offset.
429: *
430: * @see #getTickLabelOffset()
431: */
432: public void setTickLabelOffset(double offset) {
433: this .tickLabelOffset = offset;
434: notifyListeners(new DialLayerChangeEvent(this ));
435: }
436:
437: /**
438: * Returns the font used to draw the tick labels.
439: *
440: * @return The font (never <code>null</code>).
441: *
442: * @see #setTickLabelFont(Font)
443: */
444: public Font getTickLabelFont() {
445: return this .tickLabelFont;
446: }
447:
448: /**
449: * Sets the font used to display the tick labels.
450: *
451: * @param font the font (<code>null</code> not permitted).
452: *
453: * @see #getTickLabelFont()
454: */
455: public void setTickLabelFont(Font font) {
456: if (font == null) {
457: throw new IllegalArgumentException("Null 'font' argument.");
458: }
459: this .tickLabelFont = font;
460: notifyListeners(new DialLayerChangeEvent(this ));
461: }
462:
463: /**
464: * Returns the paint used to draw the tick labels.
465: *
466: * @return The paint (<code>null</code> not permitted).
467: */
468: public Paint getTickLabelPaint() {
469: return this .tickLabelPaint;
470: }
471:
472: /**
473: * Sets the paint used to draw the tick labels.
474: *
475: * @param paint the paint (<code>null</code> not permitted).
476: */
477: public void setTickLabelPaint(Paint paint) {
478: // TODO: validation
479: this .tickLabelPaint = paint;
480: notifyListeners(new DialLayerChangeEvent(this ));
481: }
482:
483: /**
484: * Returns <code>true</code> if the tick labels should be displayed,
485: * and <code>false</code> otherwise.
486: *
487: * @return A boolean.
488: */
489: public boolean getTickLabelsVisible() {
490: return this .tickLabelsVisible;
491: }
492:
493: /**
494: * Sets the flag that controls whether or not the tick labels are
495: * displayed, and sends a {@link DialLayerChangeEvent} to all registered
496: * listeners.
497: *
498: * @param visible the new flag value.
499: */
500: public void setTickLabelsVisible(boolean visible) {
501: this .tickLabelsVisible = visible;
502: notifyListeners(new DialLayerChangeEvent(this ));
503: }
504:
505: /**
506: * Returns a flag that controls whether or not the first tick label is
507: * visible.
508: *
509: * @return A boolean.
510: */
511: public boolean getFirstTickLabelVisible() {
512: return this .firstTickLabelVisible;
513: }
514:
515: /**
516: * Sets a flag that controls whether or not the first tick label is
517: * visible, and sends a {@link DialLayerChangeEvent} to all registered
518: * listeners.
519: *
520: * @param visible the new flag value.
521: */
522: public void setFirstTickLabelVisible(boolean visible) {
523: this .firstTickLabelVisible = visible;
524: notifyListeners(new DialLayerChangeEvent(this ));
525: }
526:
527: /**
528: * Returns <code>true</code> to indicate that this layer should be
529: * clipped within the dial window.
530: *
531: * @return <code>true</code>.
532: */
533: public boolean isClippedToWindow() {
534: return true;
535: }
536:
537: /**
538: * Draws the scale on the dial plot.
539: *
540: * @param g2 the graphics target (<code>null</code> not permitted).
541: * @param plot the dial plot (<code>null</code> not permitted).
542: * @param frame the reference frame that is used to construct the
543: * geometry of the plot (<code>null</code> not permitted).
544: * @param view the visible part of the plot (<code>null</code> not
545: * permitted).
546: */
547: public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
548: Rectangle2D view) {
549:
550: Rectangle2D arcRect = DialPlot.rectangleByRadius(frame,
551: this .tickRadius, this .tickRadius);
552: Rectangle2D arcRectInner = DialPlot.rectangleByRadius(frame,
553: this .tickRadius - this .minorTickLength, this .tickRadius
554: - this .minorTickLength);
555: Rectangle2D arcRectForLabels = DialPlot.rectangleByRadius(
556: frame, this .tickRadius - this .tickLabelOffset,
557: this .tickRadius - this .tickLabelOffset);
558:
559: boolean firstLabel = true;
560:
561: Arc2D arc = new Arc2D.Double();
562: for (double v = this .lowerBound; v <= this .upperBound; v += this .majorTickIncrement) {
563: arc.setArc(arcRect, this .startAngle, valueToAngle(v)
564: - this .startAngle, Arc2D.OPEN);
565: Point2D pt0 = arc.getEndPoint();
566: arc.setArc(arcRectInner, this .startAngle, valueToAngle(v)
567: - this .startAngle, Arc2D.OPEN);
568: Point2D pt1 = arc.getEndPoint();
569: g2.setPaint(this .majorTickPaint);
570: g2.setStroke(this .majorTickStroke);
571: g2.draw(new Line2D.Double(pt0, pt1));
572: arc.setArc(arcRectForLabels, this .startAngle,
573: valueToAngle(v) - this .startAngle, Arc2D.OPEN);
574: Point2D pt2 = arc.getEndPoint();
575:
576: if (tickLabelsVisible) {
577: if (!firstLabel || this .firstTickLabelVisible) {
578: g2.setFont(this .tickLabelFont);
579: TextUtilities.drawAlignedString(String.valueOf(v),
580: g2, (float) pt2.getX(), (float) pt2.getY(),
581: TextAnchor.CENTER);
582: }
583: }
584: firstLabel = false;
585:
586: // now do the minor tick marks
587: if (this .minorTickCount > 0) {
588: double minorTickIncrement = this .majorTickIncrement
589: / (this .minorTickCount + 1);
590: for (int i = 0; i < this .minorTickCount; i++) {
591: double vv = v + ((i + 1) * minorTickIncrement);
592: if (vv >= this .upperBound) {
593: break;
594: }
595: double angle = valueToAngle(vv);
596:
597: arc.setArc(arcRect, this .startAngle, angle
598: - this .startAngle, Arc2D.OPEN);
599: pt0 = arc.getEndPoint();
600: arc.setArc(arcRectInner, this .startAngle, angle
601: - this .startAngle, Arc2D.OPEN);
602: Point2D pt3 = arc.getEndPoint();
603: g2.setStroke(new BasicStroke(1.0f));
604: g2.draw(new Line2D.Double(pt0, pt3));
605: }
606: }
607:
608: }
609: }
610:
611: /**
612: * Converts a data value to an angle against this scale.
613: *
614: * @param value the data value.
615: *
616: * @return The angle (in degrees, using the same specification as Java's
617: * Arc2D class).
618: */
619: public double valueToAngle(double value) {
620: double range = this .upperBound - this .lowerBound;
621: double unit = this .extent / range;
622: return this .startAngle + unit * (value - this .lowerBound);
623: }
624:
625: public double angleToValue(double angle) {
626: return Double.NaN; // FIXME
627: }
628:
629: /**
630: * Tests this <code>StandardDialScale</code> for equality with an arbitrary
631: * object.
632: *
633: * @param obj the object (<code>null</code> permitted).
634: *
635: * @return A boolean.
636: */
637: public boolean equals(Object obj) {
638: if (obj == this ) {
639: return true;
640: }
641: if (!(obj instanceof StandardDialScale)) {
642: return false;
643: }
644: StandardDialScale that = (StandardDialScale) obj;
645: if (this .lowerBound != that.lowerBound) {
646: return false;
647: }
648: if (this .upperBound != that.upperBound) {
649: return false;
650: }
651: if (this .startAngle != that.startAngle) {
652: return false;
653: }
654: if (this .extent != that.extent) {
655: return false;
656: }
657: if (this .tickRadius != that.tickRadius) {
658: return false;
659: }
660: if (this .majorTickIncrement != that.majorTickIncrement) {
661: return false;
662: }
663: if (this .majorTickLength != that.majorTickLength) {
664: return false;
665: }
666: if (!PaintUtilities.equal(this .majorTickPaint,
667: that.majorTickPaint)) {
668: return false;
669: }
670: if (!this .majorTickStroke.equals(that.majorTickStroke)) {
671: return false;
672: }
673: if (this .minorTickCount != that.minorTickCount) {
674: return false;
675: }
676: if (this .minorTickLength != that.minorTickLength) {
677: return false;
678: }
679: if (this .tickLabelOffset != that.tickLabelOffset) {
680: return false;
681: }
682: if (!this .tickLabelFont.equals(that.tickLabelFont)) {
683: return false;
684: }
685: if (!PaintUtilities.equal(this .tickLabelPaint,
686: that.tickLabelPaint)) {
687: return false;
688: }
689: return true;
690: }
691:
692: /**
693: * Returns a clone of this instance.
694: *
695: * @return A clone.
696: *
697: * @throws CloneNotSupportedException if this instance is not cloneable.
698: */
699: public Object clone() throws CloneNotSupportedException {
700: return super .clone();
701: }
702:
703: /**
704: * Provides serialization support.
705: *
706: * @param stream the output stream.
707: *
708: * @throws IOException if there is an I/O error.
709: */
710: private void writeObject(ObjectOutputStream stream)
711: throws IOException {
712: stream.defaultWriteObject();
713: SerialUtilities.writePaint(this .majorTickPaint, stream);
714: SerialUtilities.writeStroke(this .majorTickStroke, stream);
715: SerialUtilities.writePaint(this .tickLabelPaint, stream);
716: }
717:
718: /**
719: * Provides serialization support.
720: *
721: * @param stream the input stream.
722: *
723: * @throws IOException if there is an I/O error.
724: * @throws ClassNotFoundException if there is a classpath problem.
725: */
726: private void readObject(ObjectInputStream stream)
727: throws IOException, ClassNotFoundException {
728: stream.defaultReadObject();
729: this.majorTickPaint = SerialUtilities.readPaint(stream);
730: this.majorTickStroke = SerialUtilities.readStroke(stream);
731: this.tickLabelPaint = SerialUtilities.readPaint(stream);
732: }
733:
734: }
|