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