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("Null 'location' argument.");
209 }
210 this.axisLocation = location;
211 notifyListeners(new TitleChangeEvent(this));
212 }
213
214 /**
215 * Returns the offset between the axis and the paint strip.
216 *
217 * @return The offset between the axis and the paint strip.
218 *
219 * @see #setAxisOffset(double)
220 */
221 public double getAxisOffset() {
222 return this.axisOffset;
223 }
224
225 /**
226 * Sets the offset between the axis and the paint strip and sends a
227 * {@link TitleChangeEvent} to all registered listeners.
228 *
229 * @param offset the offset.
230 */
231 public void setAxisOffset(double offset) {
232 this.axisOffset = offset;
233 notifyListeners(new TitleChangeEvent(this));
234 }
235
236 /**
237 * Returns the width of the paint strip, in Java2D units.
238 *
239 * @return The width of the paint strip.
240 *
241 * @see #setStripWidth(double)
242 */
243 public double getStripWidth() {
244 return this.stripWidth;
245 }
246
247 /**
248 * Sets the width of the paint strip and sends a {@link TitleChangeEvent}
249 * to all registered listeners.
250 *
251 * @param width the width.
252 *
253 * @see #getStripWidth()
254 */
255 public void setStripWidth(double width) {
256 this.stripWidth = width;
257 notifyListeners(new TitleChangeEvent(this));
258 }
259
260 /**
261 * Returns the flag that controls whether or not an outline is drawn
262 * around the paint strip.
263 *
264 * @return A boolean.
265 *
266 * @see #setStripOutlineVisible(boolean)
267 */
268 public boolean isStripOutlineVisible() {
269 return this.stripOutlineVisible;
270 }
271
272 /**
273 * Sets the flag that controls whether or not an outline is drawn around
274 * the paint strip, and sends a {@link TitleChangeEvent} to all registered
275 * listeners.
276 *
277 * @param visible the flag.
278 *
279 * @see #isStripOutlineVisible()
280 */
281 public void setStripOutlineVisible(boolean visible) {
282 this.stripOutlineVisible = visible;
283 notifyListeners(new TitleChangeEvent(this));
284 }
285
286 /**
287 * Returns the paint used to draw the outline of the paint strip.
288 *
289 * @return The paint (never <code>null</code>).
290 *
291 * @see #setStripOutlinePaint(Paint)
292 */
293 public Paint getStripOutlinePaint() {
294 return this.stripOutlinePaint;
295 }
296
297 /**
298 * Sets the paint used to draw the outline of the paint strip, and sends
299 * a {@link TitleChangeEvent} to all registered listeners.
300 *
301 * @param paint the paint (<code>null</code> not permitted).
302 *
303 * @see #getStripOutlinePaint()
304 */
305 public void setStripOutlinePaint(Paint paint) {
306 if (paint == null) {
307 throw new IllegalArgumentException("Null 'paint' argument.");
308 }
309 this.stripOutlinePaint = paint;
310 notifyListeners(new TitleChangeEvent(this));
311 }
312
313 /**
314 * Returns the stroke used to draw the outline around the paint strip.
315 *
316 * @return The stroke (never <code>null</code>).
317 *
318 * @see #setStripOutlineStroke(Stroke)
319 */
320 public Stroke getStripOutlineStroke() {
321 return this.stripOutlineStroke;
322 }
323
324 /**
325 * Sets the stroke used to draw the outline around the paint strip and
326 * sends a {@link TitleChangeEvent} to all registered listeners.
327 *
328 * @param stroke the stroke (<code>null</code> not permitted).
329 *
330 * @see #getStripOutlineStroke()
331 */
332 public void setStripOutlineStroke(Stroke stroke) {
333 if (stroke == null) {
334 throw new IllegalArgumentException("Null 'stroke' argument.");
335 }
336 this.stripOutlineStroke = stroke;
337 notifyListeners(new TitleChangeEvent(this));
338 }
339
340 /**
341 * Returns the background paint.
342 *
343 * @return The background paint.
344 */
345 public Paint getBackgroundPaint() {
346 return this.backgroundPaint;
347 }
348
349 /**
350 * Sets the background paint and sends a {@link TitleChangeEvent} to all
351 * registered listeners.
352 *
353 * @param paint the paint (<code>null</code> permitted).
354 */
355 public void setBackgroundPaint(Paint paint) {
356 this.backgroundPaint = paint;
357 notifyListeners(new TitleChangeEvent(this));
358 }
359
360 /**
361 * Arranges the contents of the block, within the given constraints, and
362 * returns the block size.
363 *
364 * @param g2 the graphics device.
365 * @param constraint the constraint (<code>null</code> not permitted).
366 *
367 * @return The block size (in Java2D units, never <code>null</code>).
368 */
369 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
370 RectangleConstraint cc = toContentConstraint(constraint);
371 LengthConstraintType w = cc.getWidthConstraintType();
372 LengthConstraintType h = cc.getHeightConstraintType();
373 Size2D contentSize = null;
374 if (w == LengthConstraintType.NONE) {
375 if (h == LengthConstraintType.NONE) {
376 contentSize = new Size2D(getWidth(), getHeight());
377 }
378 else if (h == LengthConstraintType.RANGE) {
379 throw new RuntimeException("Not yet implemented.");
380 }
381 else if (h == LengthConstraintType.FIXED) {
382 throw new RuntimeException("Not yet implemented.");
383 }
384 }
385 else if (w == LengthConstraintType.RANGE) {
386 if (h == LengthConstraintType.NONE) {
387 throw new RuntimeException("Not yet implemented.");
388 }
389 else if (h == LengthConstraintType.RANGE) {
390 contentSize = arrangeRR(g2, cc.getWidthRange(),
391 cc.getHeightRange());
392 }
393 else if (h == LengthConstraintType.FIXED) {
394 throw new RuntimeException("Not yet implemented.");
395 }
396 }
397 else if (w == LengthConstraintType.FIXED) {
398 if (h == LengthConstraintType.NONE) {
399 throw new RuntimeException("Not yet implemented.");
400 }
401 else if (h == LengthConstraintType.RANGE) {
402 throw new RuntimeException("Not yet implemented.");
403 }
404 else if (h == LengthConstraintType.FIXED) {
405 throw new RuntimeException("Not yet implemented.");
406 }
407 }
408 return new Size2D(calculateTotalWidth(contentSize.getWidth()),
409 calculateTotalHeight(contentSize.getHeight()));
410 }
411
412 /**
413 * Returns the content size for the title. This will reflect the fact that
414 * a text title positioned on the left or right of a chart will be rotated
415 * 90 degrees.
416 *
417 * @param g2 the graphics device.
418 * @param widthRange the width range.
419 * @param heightRange the height range.
420 *
421 * @return The content size.
422 */
423 protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
424 Range heightRange) {
425
426 RectangleEdge position = getPosition();
427 if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
428
429
430 float maxWidth = (float) widthRange.getUpperBound();
431
432 // determine the space required for the axis
433 AxisSpace space = this.axis.reserveSpace(g2, null,
434 new Rectangle2D.Double(0, 0, maxWidth, 100),
435 RectangleEdge.BOTTOM, null);
436
437 return new Size2D(maxWidth, this.stripWidth + this.axisOffset
438 + space.getTop() + space.getBottom());
439 }
440 else if (position == RectangleEdge.LEFT || position
441 == RectangleEdge.RIGHT) {
442 float maxHeight = (float) heightRange.getUpperBound();
443 AxisSpace space = this.axis.reserveSpace(g2, null,
444 new Rectangle2D.Double(0, 0, 100, maxHeight),
445 RectangleEdge.RIGHT, null);
446 return new Size2D(this.stripWidth + this.axisOffset
447 + space.getLeft() + space.getRight(), maxHeight);
448 }
449 else {
450 throw new RuntimeException("Unrecognised position.");
451 }
452 }
453
454 /**
455 * Draws the legend within the specified area.
456 *
457 * @param g2 the graphics target (<code>null</code> not permitted).
458 * @param area the drawing area (<code>null</code> not permitted).
459 */
460 public void draw(Graphics2D g2, Rectangle2D area) {
461 draw(g2, area, null);
462 }
463
464 /**
465 * The number of subdivisions to use when drawing the paint strip. Maybe
466 * this need to be user controllable?
467 */
468 private static final int SUBDIVISIONS = 200;
469
470 /**
471 * Draws the legend within the specified area.
472 *
473 * @param g2 the graphics target (<code>null</code> not permitted).
474 * @param area the drawing area (<code>null</code> not permitted).
475 * @param params drawing parameters (ignored here).
476 *
477 * @return <code>null</code>.
478 */
479 public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
480
481 Rectangle2D target = (Rectangle2D) area.clone();
482 target = trimMargin(target);
483 if (this.backgroundPaint != null) {
484 g2.setPaint(this.backgroundPaint);
485 g2.fill(target);
486 }
487 getBorder().draw(g2, target);
488 getBorder().getInsets().trim(target);
489 target = trimPadding(target);
490 double base = this.axis.getLowerBound();
491 double increment = this.axis.getRange().getLength() / SUBDIVISIONS;
492 Rectangle2D r = new Rectangle2D.Double();
493
494
495 if (RectangleEdge.isTopOrBottom(getPosition())) {
496 RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
497 this.axisLocation, PlotOrientation.HORIZONTAL);
498 double ww = Math.ceil(target.getWidth() / SUBDIVISIONS);
499 if (axisEdge == RectangleEdge.TOP) {
500 for (int i = 0; i < SUBDIVISIONS; i++) {
501 double v = base + (i * increment);
502 Paint p = this.scale.getPaint(v);
503 double vv = this.axis.valueToJava2D(v, target,
504 RectangleEdge.BOTTOM);
505 r.setRect(vv, target.getMaxY() - this.stripWidth, ww,
506 this.stripWidth);
507 g2.setPaint(p);
508 g2.fill(r);
509 }
510 g2.setPaint(this.stripOutlinePaint);
511 g2.setStroke(this.stripOutlineStroke);
512 g2.draw(new Rectangle2D.Double(target.getMinX(),
513 target.getMaxY() - this.stripWidth, target.getWidth(),
514 this.stripWidth));
515 this.axis.draw(g2, target.getMaxY() - this.stripWidth
516 - this.axisOffset, target, target, RectangleEdge.TOP,
517 null);
518 }
519 else if (axisEdge == RectangleEdge.BOTTOM) {
520 for (int i = 0; i < SUBDIVISIONS; i++) {
521 double v = base + (i * increment);
522 Paint p = this.scale.getPaint(v);
523 double vv = this.axis.valueToJava2D(v, target,
524 RectangleEdge.BOTTOM);
525 r.setRect(vv, target.getMinY(), ww, this.stripWidth);
526 g2.setPaint(p);
527 g2.fill(r);
528 }
529 g2.setPaint(this.stripOutlinePaint);
530 g2.setStroke(this.stripOutlineStroke);
531 g2.draw(new Rectangle2D.Double(target.getMinX(),
532 target.getMinY(), target.getWidth(), this.stripWidth));
533 this.axis.draw(g2, target.getMinY() + this.stripWidth
534 + this.axisOffset, target, target,
535 RectangleEdge.BOTTOM, null);
536 }
537 }
538 else {
539 RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
540 this.axisLocation, PlotOrientation.VERTICAL);
541 double hh = Math.ceil(target.getHeight() / SUBDIVISIONS);
542 if (axisEdge == RectangleEdge.LEFT) {
543 for (int i = 0; i < SUBDIVISIONS; i++) {
544 double v = base + (i * increment);
545 Paint p = this.scale.getPaint(v);
546 double vv = this.axis.valueToJava2D(v, target,
547 RectangleEdge.LEFT);
548 r.setRect(target.getMaxX() - this.stripWidth, vv - hh,
549 this.stripWidth, hh);
550 g2.setPaint(p);
551 g2.fill(r);
552 }
553 g2.setPaint(this.stripOutlinePaint);
554 g2.setStroke(this.stripOutlineStroke);
555 g2.draw(new Rectangle2D.Double(target.getMaxX()
556 - this.stripWidth, target.getMinY(), this.stripWidth,
557 target.getHeight()));
558 this.axis.draw(g2, target.getMaxX() - this.stripWidth
559 - this.axisOffset, target, target, RectangleEdge.LEFT,
560 null);
561 }
562 else if (axisEdge == RectangleEdge.RIGHT) {
563 for (int i = 0; i < SUBDIVISIONS; i++) {
564 double v = base + (i * increment);
565 Paint p = this.scale.getPaint(v);
566 double vv = this.axis.valueToJava2D(v, target,
567 RectangleEdge.LEFT);
568 r.setRect(target.getMinX(), vv - hh, this.stripWidth, hh);
569 g2.setPaint(p);
570 g2.fill(r);
571 }
572 g2.setPaint(this.stripOutlinePaint);
573 g2.setStroke(this.stripOutlineStroke);
574 g2.draw(new Rectangle2D.Double(target.getMinX(),
575 target.getMinY(), this.stripWidth, target.getHeight()));
576 this.axis.draw(g2, target.getMinX() + this.stripWidth
577 + this.axisOffset, target, target, RectangleEdge.RIGHT,
578 null);
579 }
580 }
581 return null;
582 }
583
584 /**
585 * Tests this legend for equality with an arbitrary object.
586 *
587 * @param obj the object (<code>null</code> permitted).
588 *
589 * @return A boolean.
590 */
591 public boolean equals(Object obj) {
592 if (!(obj instanceof PaintScaleLegend)) {
593 return false;
594 }
595 PaintScaleLegend that = (PaintScaleLegend) obj;
596 if (!this.scale.equals(that.scale)) {
597 return false;
598 }
599 if (!this.axis.equals(that.axis)) {
600 return false;
601 }
602 if (!this.axisLocation.equals(that.axisLocation)) {
603 return false;
604 }
605 if (this.axisOffset != that.axisOffset) {
606 return false;
607 }
608 if (this.stripWidth != that.stripWidth) {
609 return false;
610 }
611 if (this.stripOutlineVisible != that.stripOutlineVisible) {
612 return false;
613 }
614 if (!PaintUtilities.equal(this.stripOutlinePaint,
615 that.stripOutlinePaint)) {
616 return false;
617 }
618 if (!this.stripOutlineStroke.equals(that.stripOutlineStroke)) {
619 return false;
620 }
621 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
622 return false;
623 }
624 return super.equals(obj);
625 }
626
627 /**
628 * Provides serialization support.
629 *
630 * @param stream the output stream.
631 *
632 * @throws IOException if there is an I/O error.
633 */
634 private void writeObject(ObjectOutputStream stream) 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 }