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: * PaintScaleLegend.java
029: * ---------------------
030: * (C) Copyright 2007, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: PaintScaleLegend.java,v 1.1.2.1 2007/01/31 14:15:16 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 22-Jan-2007 : Version 1 (DG);
040: *
041: */
042:
043: package org.jfree.chart.title;
044:
045: import java.awt.BasicStroke;
046: import java.awt.Color;
047: import java.awt.Graphics2D;
048: import java.awt.Paint;
049: import java.awt.Stroke;
050: import java.awt.geom.Rectangle2D;
051: import java.io.IOException;
052: import java.io.ObjectInputStream;
053: import java.io.ObjectOutputStream;
054:
055: import org.jfree.chart.axis.AxisLocation;
056: import org.jfree.chart.axis.AxisSpace;
057: import org.jfree.chart.axis.ValueAxis;
058: import org.jfree.chart.block.LengthConstraintType;
059: import org.jfree.chart.block.RectangleConstraint;
060: import org.jfree.chart.event.TitleChangeEvent;
061: import org.jfree.chart.plot.Plot;
062: import org.jfree.chart.plot.PlotOrientation;
063: import org.jfree.chart.renderer.PaintScale;
064: import org.jfree.data.Range;
065: import org.jfree.io.SerialUtilities;
066: import org.jfree.ui.RectangleEdge;
067: import org.jfree.ui.Size2D;
068: import org.jfree.util.PaintUtilities;
069: import org.jfree.util.PublicCloneable;
070:
071: /**
072: * A legend that shows a range of values and their associated colors, driven
073: * by an underlying {@link PaintScale} implementation.
074: *
075: * @since 1.0.4
076: */
077: public class PaintScaleLegend extends Title implements PublicCloneable {
078:
079: /** The paint scale (never <code>null</code>). */
080: private PaintScale scale;
081:
082: /** The value axis (never <code>null</code>). */
083: private ValueAxis axis;
084:
085: /**
086: * The axis location (handles both orientations, never
087: * <code>null</code>).
088: */
089: private AxisLocation axisLocation;
090:
091: /** The offset between the axis and the paint strip (in Java2D units). */
092: private double axisOffset;
093:
094: /** The thickness of the paint strip (in Java2D units). */
095: private double stripWidth;
096:
097: /**
098: * A flag that controls whether or not an outline is drawn around the
099: * paint strip.
100: */
101: private boolean stripOutlineVisible;
102:
103: /** The paint used to draw an outline around the paint strip. */
104: private transient Paint stripOutlinePaint;
105:
106: /** The stroke used to draw an outline around the paint strip. */
107: private transient Stroke stripOutlineStroke;
108:
109: /** The background paint (never <code>null</code>). */
110: private transient Paint backgroundPaint;
111:
112: /**
113: * Creates a new instance.
114: *
115: * @param scale the scale (<code>null</code> not permitted).
116: * @param axis the axis (<code>null</code> not permitted).
117: */
118: public PaintScaleLegend(PaintScale scale, ValueAxis axis) {
119: if (axis == null) {
120: throw new IllegalArgumentException("Null 'axis' argument.");
121: }
122: this .scale = scale;
123: this .axis = axis;
124: this .axisLocation = AxisLocation.BOTTOM_OR_LEFT;
125: this .axisOffset = 0.0;
126: this .stripWidth = 15.0;
127: this .stripOutlineVisible = false;
128: this .stripOutlinePaint = Color.gray;
129: this .stripOutlineStroke = new BasicStroke(0.5f);
130: this .backgroundPaint = Color.white;
131: }
132:
133: /**
134: * Returns the scale used to convert values to colors.
135: *
136: * @return The scale (never <code>null</code>).
137: *
138: * @see #setScale(PaintScale)
139: */
140: public PaintScale getScale() {
141: return this .scale;
142: }
143:
144: /**
145: * Sets the scale and sends a {@link TitleChangeEvent} to all registered
146: * listeners.
147: *
148: * @param scale the scale (<code>null</code> not permitted).
149: *
150: * @see #getScale()
151: */
152: public void setScale(PaintScale scale) {
153: if (scale == null) {
154: throw new IllegalArgumentException("Null 'scale' argument.");
155: }
156: this .scale = scale;
157: notifyListeners(new TitleChangeEvent(this ));
158: }
159:
160: /**
161: * Returns the axis for the paint scale.
162: *
163: * @return The axis (never <code>null</code>).
164: *
165: * @see #setAxis(ValueAxis)
166: */
167: public ValueAxis getAxis() {
168: return this .axis;
169: }
170:
171: /**
172: * Sets the axis for the paint scale and sends a {@link TitleChangeEvent}
173: * to all registered listeners.
174: *
175: * @param axis the axis (<code>null</code> not permitted).
176: *
177: * @see #getAxis()
178: */
179: public void setAxis(ValueAxis axis) {
180: if (axis == null) {
181: throw new IllegalArgumentException("Null 'axis' argument.");
182: }
183: this .axis = axis;
184: notifyListeners(new TitleChangeEvent(this ));
185: }
186:
187: /**
188: * Returns the axis location.
189: *
190: * @return The axis location (never <code>null</code>).
191: *
192: * @see #setAxisLocation(AxisLocation)
193: */
194: public AxisLocation getAxisLocation() {
195: return this .axisLocation;
196: }
197:
198: /**
199: * Sets the axis location and sends a {@link TitleChangeEvent} to all
200: * registered listeners.
201: *
202: * @param location the location (<code>null</code> not permitted).
203: *
204: * @see #getAxisLocation()
205: */
206: public void setAxisLocation(AxisLocation location) {
207: if (location == null) {
208: throw new IllegalArgumentException(
209: "Null 'location' argument.");
210: }
211: this .axisLocation = location;
212: notifyListeners(new TitleChangeEvent(this ));
213: }
214:
215: /**
216: * Returns the offset between the axis and the paint strip.
217: *
218: * @return The offset between the axis and the paint strip.
219: *
220: * @see #setAxisOffset(double)
221: */
222: public double getAxisOffset() {
223: return this .axisOffset;
224: }
225:
226: /**
227: * Sets the offset between the axis and the paint strip and sends a
228: * {@link TitleChangeEvent} to all registered listeners.
229: *
230: * @param offset the offset.
231: */
232: public void setAxisOffset(double offset) {
233: this .axisOffset = offset;
234: notifyListeners(new TitleChangeEvent(this ));
235: }
236:
237: /**
238: * Returns the width of the paint strip, in Java2D units.
239: *
240: * @return The width of the paint strip.
241: *
242: * @see #setStripWidth(double)
243: */
244: public double getStripWidth() {
245: return this .stripWidth;
246: }
247:
248: /**
249: * Sets the width of the paint strip and sends a {@link TitleChangeEvent}
250: * to all registered listeners.
251: *
252: * @param width the width.
253: *
254: * @see #getStripWidth()
255: */
256: public void setStripWidth(double width) {
257: this .stripWidth = width;
258: notifyListeners(new TitleChangeEvent(this ));
259: }
260:
261: /**
262: * Returns the flag that controls whether or not an outline is drawn
263: * around the paint strip.
264: *
265: * @return A boolean.
266: *
267: * @see #setStripOutlineVisible(boolean)
268: */
269: public boolean isStripOutlineVisible() {
270: return this .stripOutlineVisible;
271: }
272:
273: /**
274: * Sets the flag that controls whether or not an outline is drawn around
275: * the paint strip, and sends a {@link TitleChangeEvent} to all registered
276: * listeners.
277: *
278: * @param visible the flag.
279: *
280: * @see #isStripOutlineVisible()
281: */
282: public void setStripOutlineVisible(boolean visible) {
283: this .stripOutlineVisible = visible;
284: notifyListeners(new TitleChangeEvent(this ));
285: }
286:
287: /**
288: * Returns the paint used to draw the outline of the paint strip.
289: *
290: * @return The paint (never <code>null</code>).
291: *
292: * @see #setStripOutlinePaint(Paint)
293: */
294: public Paint getStripOutlinePaint() {
295: return this .stripOutlinePaint;
296: }
297:
298: /**
299: * Sets the paint used to draw the outline of the paint strip, and sends
300: * a {@link TitleChangeEvent} to all registered listeners.
301: *
302: * @param paint the paint (<code>null</code> not permitted).
303: *
304: * @see #getStripOutlinePaint()
305: */
306: public void setStripOutlinePaint(Paint paint) {
307: if (paint == null) {
308: throw new IllegalArgumentException("Null 'paint' argument.");
309: }
310: this .stripOutlinePaint = paint;
311: notifyListeners(new TitleChangeEvent(this ));
312: }
313:
314: /**
315: * Returns the stroke used to draw the outline around the paint strip.
316: *
317: * @return The stroke (never <code>null</code>).
318: *
319: * @see #setStripOutlineStroke(Stroke)
320: */
321: public Stroke getStripOutlineStroke() {
322: return this .stripOutlineStroke;
323: }
324:
325: /**
326: * Sets the stroke used to draw the outline around the paint strip and
327: * sends a {@link TitleChangeEvent} to all registered listeners.
328: *
329: * @param stroke the stroke (<code>null</code> not permitted).
330: *
331: * @see #getStripOutlineStroke()
332: */
333: public void setStripOutlineStroke(Stroke stroke) {
334: if (stroke == null) {
335: throw new IllegalArgumentException(
336: "Null 'stroke' argument.");
337: }
338: this .stripOutlineStroke = stroke;
339: notifyListeners(new TitleChangeEvent(this ));
340: }
341:
342: /**
343: * Returns the background paint.
344: *
345: * @return The background paint.
346: */
347: public Paint getBackgroundPaint() {
348: return this .backgroundPaint;
349: }
350:
351: /**
352: * Sets the background paint and sends a {@link TitleChangeEvent} to all
353: * registered listeners.
354: *
355: * @param paint the paint (<code>null</code> permitted).
356: */
357: public void setBackgroundPaint(Paint paint) {
358: this .backgroundPaint = paint;
359: notifyListeners(new TitleChangeEvent(this ));
360: }
361:
362: /**
363: * Arranges the contents of the block, within the given constraints, and
364: * returns the block size.
365: *
366: * @param g2 the graphics device.
367: * @param constraint the constraint (<code>null</code> not permitted).
368: *
369: * @return The block size (in Java2D units, never <code>null</code>).
370: */
371: public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
372: RectangleConstraint cc = toContentConstraint(constraint);
373: LengthConstraintType w = cc.getWidthConstraintType();
374: LengthConstraintType h = cc.getHeightConstraintType();
375: Size2D contentSize = null;
376: if (w == LengthConstraintType.NONE) {
377: if (h == LengthConstraintType.NONE) {
378: contentSize = new Size2D(getWidth(), getHeight());
379: } else if (h == LengthConstraintType.RANGE) {
380: throw new RuntimeException("Not yet implemented.");
381: } else if (h == LengthConstraintType.FIXED) {
382: throw new RuntimeException("Not yet implemented.");
383: }
384: } else if (w == LengthConstraintType.RANGE) {
385: if (h == LengthConstraintType.NONE) {
386: throw new RuntimeException("Not yet implemented.");
387: } else if (h == LengthConstraintType.RANGE) {
388: contentSize = arrangeRR(g2, cc.getWidthRange(), cc
389: .getHeightRange());
390: } else if (h == LengthConstraintType.FIXED) {
391: throw new RuntimeException("Not yet implemented.");
392: }
393: } else if (w == LengthConstraintType.FIXED) {
394: if (h == LengthConstraintType.NONE) {
395: throw new RuntimeException("Not yet implemented.");
396: } else if (h == LengthConstraintType.RANGE) {
397: throw new RuntimeException("Not yet implemented.");
398: } else if (h == LengthConstraintType.FIXED) {
399: throw new RuntimeException("Not yet implemented.");
400: }
401: }
402: return new Size2D(calculateTotalWidth(contentSize.getWidth()),
403: calculateTotalHeight(contentSize.getHeight()));
404: }
405:
406: /**
407: * Returns the content size for the title. This will reflect the fact that
408: * a text title positioned on the left or right of a chart will be rotated
409: * 90 degrees.
410: *
411: * @param g2 the graphics device.
412: * @param widthRange the width range.
413: * @param heightRange the height range.
414: *
415: * @return The content size.
416: */
417: protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
418: Range heightRange) {
419:
420: RectangleEdge position = getPosition();
421: if (position == RectangleEdge.TOP
422: || position == RectangleEdge.BOTTOM) {
423:
424: float maxWidth = (float) widthRange.getUpperBound();
425:
426: // determine the space required for the axis
427: AxisSpace space = this .axis.reserveSpace(g2, null,
428: new Rectangle2D.Double(0, 0, maxWidth, 100),
429: RectangleEdge.BOTTOM, null);
430:
431: return new Size2D(maxWidth, this .stripWidth
432: + this .axisOffset + space.getTop()
433: + space.getBottom());
434: } else if (position == RectangleEdge.LEFT
435: || position == RectangleEdge.RIGHT) {
436: float maxHeight = (float) heightRange.getUpperBound();
437: AxisSpace space = this .axis.reserveSpace(g2, null,
438: new Rectangle2D.Double(0, 0, 100, maxHeight),
439: RectangleEdge.RIGHT, null);
440: return new Size2D(this .stripWidth + this .axisOffset
441: + space.getLeft() + space.getRight(), maxHeight);
442: } else {
443: throw new RuntimeException("Unrecognised position.");
444: }
445: }
446:
447: /**
448: * Draws the legend within the specified area.
449: *
450: * @param g2 the graphics target (<code>null</code> not permitted).
451: * @param area the drawing area (<code>null</code> not permitted).
452: */
453: public void draw(Graphics2D g2, Rectangle2D area) {
454: draw(g2, area, null);
455: }
456:
457: /**
458: * The number of subdivisions to use when drawing the paint strip. Maybe
459: * this need to be user controllable?
460: */
461: private static final int SUBDIVISIONS = 200;
462:
463: /**
464: * Draws the legend within the specified area.
465: *
466: * @param g2 the graphics target (<code>null</code> not permitted).
467: * @param area the drawing area (<code>null</code> not permitted).
468: * @param params drawing parameters (ignored here).
469: *
470: * @return <code>null</code>.
471: */
472: public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
473:
474: Rectangle2D target = (Rectangle2D) area.clone();
475: target = trimMargin(target);
476: if (this .backgroundPaint != null) {
477: g2.setPaint(this .backgroundPaint);
478: g2.fill(target);
479: }
480: getBorder().draw(g2, target);
481: getBorder().getInsets().trim(target);
482: target = trimPadding(target);
483: double base = this .axis.getLowerBound();
484: double increment = this .axis.getRange().getLength()
485: / SUBDIVISIONS;
486: Rectangle2D r = new Rectangle2D.Double();
487:
488: if (RectangleEdge.isTopOrBottom(getPosition())) {
489: RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
490: this .axisLocation, PlotOrientation.HORIZONTAL);
491: double ww = Math.ceil(target.getWidth() / SUBDIVISIONS);
492: if (axisEdge == RectangleEdge.TOP) {
493: for (int i = 0; i < SUBDIVISIONS; i++) {
494: double v = base + (i * increment);
495: Paint p = this .scale.getPaint(v);
496: double vv = this .axis.valueToJava2D(v, target,
497: RectangleEdge.BOTTOM);
498: r.setRect(vv, target.getMaxY() - this .stripWidth,
499: ww, this .stripWidth);
500: g2.setPaint(p);
501: g2.fill(r);
502: }
503: g2.setPaint(this .stripOutlinePaint);
504: g2.setStroke(this .stripOutlineStroke);
505: g2.draw(new Rectangle2D.Double(target.getMinX(), target
506: .getMaxY()
507: - this .stripWidth, target.getWidth(),
508: this .stripWidth));
509: this .axis.draw(g2, target.getMaxY() - this .stripWidth
510: - this .axisOffset, target, target,
511: RectangleEdge.TOP, null);
512: } else if (axisEdge == RectangleEdge.BOTTOM) {
513: for (int i = 0; i < SUBDIVISIONS; i++) {
514: double v = base + (i * increment);
515: Paint p = this .scale.getPaint(v);
516: double vv = this .axis.valueToJava2D(v, target,
517: RectangleEdge.BOTTOM);
518: r
519: .setRect(vv, target.getMinY(), ww,
520: this .stripWidth);
521: g2.setPaint(p);
522: g2.fill(r);
523: }
524: g2.setPaint(this .stripOutlinePaint);
525: g2.setStroke(this .stripOutlineStroke);
526: g2
527: .draw(new Rectangle2D.Double(target.getMinX(),
528: target.getMinY(), target.getWidth(),
529: this .stripWidth));
530: this .axis.draw(g2, target.getMinY() + this .stripWidth
531: + this .axisOffset, target, target,
532: RectangleEdge.BOTTOM, null);
533: }
534: } else {
535: RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
536: this .axisLocation, PlotOrientation.VERTICAL);
537: double hh = Math.ceil(target.getHeight() / SUBDIVISIONS);
538: if (axisEdge == RectangleEdge.LEFT) {
539: for (int i = 0; i < SUBDIVISIONS; i++) {
540: double v = base + (i * increment);
541: Paint p = this .scale.getPaint(v);
542: double vv = this .axis.valueToJava2D(v, target,
543: RectangleEdge.LEFT);
544: r.setRect(target.getMaxX() - this .stripWidth, vv
545: - hh, this .stripWidth, hh);
546: g2.setPaint(p);
547: g2.fill(r);
548: }
549: g2.setPaint(this .stripOutlinePaint);
550: g2.setStroke(this .stripOutlineStroke);
551: g2.draw(new Rectangle2D.Double(target.getMaxX()
552: - this .stripWidth, target.getMinY(),
553: this .stripWidth, target.getHeight()));
554: this .axis.draw(g2, target.getMaxX() - this .stripWidth
555: - this .axisOffset, target, target,
556: RectangleEdge.LEFT, null);
557: } else if (axisEdge == RectangleEdge.RIGHT) {
558: for (int i = 0; i < SUBDIVISIONS; i++) {
559: double v = base + (i * increment);
560: Paint p = this .scale.getPaint(v);
561: double vv = this .axis.valueToJava2D(v, target,
562: RectangleEdge.LEFT);
563: r.setRect(target.getMinX(), vv - hh,
564: this .stripWidth, hh);
565: g2.setPaint(p);
566: g2.fill(r);
567: }
568: g2.setPaint(this .stripOutlinePaint);
569: g2.setStroke(this .stripOutlineStroke);
570: g2
571: .draw(new Rectangle2D.Double(target.getMinX(),
572: target.getMinY(), this .stripWidth,
573: target.getHeight()));
574: this .axis.draw(g2, target.getMinX() + this .stripWidth
575: + this .axisOffset, target, target,
576: RectangleEdge.RIGHT, null);
577: }
578: }
579: return null;
580: }
581:
582: /**
583: * Tests this legend for equality with an arbitrary object.
584: *
585: * @param obj the object (<code>null</code> permitted).
586: *
587: * @return A boolean.
588: */
589: public boolean equals(Object obj) {
590: if (!(obj instanceof PaintScaleLegend)) {
591: return false;
592: }
593: PaintScaleLegend that = (PaintScaleLegend) obj;
594: if (!this .scale.equals(that.scale)) {
595: return false;
596: }
597: if (!this .axis.equals(that.axis)) {
598: return false;
599: }
600: if (!this .axisLocation.equals(that.axisLocation)) {
601: return false;
602: }
603: if (this .axisOffset != that.axisOffset) {
604: return false;
605: }
606: if (this .stripWidth != that.stripWidth) {
607: return false;
608: }
609: if (this .stripOutlineVisible != that.stripOutlineVisible) {
610: return false;
611: }
612: if (!PaintUtilities.equal(this .stripOutlinePaint,
613: that.stripOutlinePaint)) {
614: return false;
615: }
616: if (!this .stripOutlineStroke.equals(that.stripOutlineStroke)) {
617: return false;
618: }
619: if (!PaintUtilities.equal(this .backgroundPaint,
620: that.backgroundPaint)) {
621: return false;
622: }
623: return super .equals(obj);
624: }
625:
626: /**
627: * Provides serialization support.
628: *
629: * @param stream the output stream.
630: *
631: * @throws IOException if there is an I/O error.
632: */
633: private void writeObject(ObjectOutputStream stream)
634: throws IOException {
635: stream.defaultWriteObject();
636: SerialUtilities.writePaint(this .backgroundPaint, stream);
637: SerialUtilities.writePaint(this .stripOutlinePaint, stream);
638: SerialUtilities.writeStroke(this .stripOutlineStroke, stream);
639: }
640:
641: /**
642: * Provides serialization support.
643: *
644: * @param stream the input stream.
645: *
646: * @throws IOException if there is an I/O error.
647: * @throws ClassNotFoundException if there is a classpath problem.
648: */
649: private void readObject(ObjectInputStream stream)
650: throws IOException, ClassNotFoundException {
651: stream.defaultReadObject();
652: this.backgroundPaint = SerialUtilities.readPaint(stream);
653: this.stripOutlinePaint = SerialUtilities.readPaint(stream);
654: this.stripOutlineStroke = SerialUtilities.readStroke(stream);
655: }
656:
657: }
|