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