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 * LegendGraphic.java
029 * ------------------
030 * (C) Copyright 2004-2008, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * Changes
036 * -------
037 * 26-Oct-2004 : Version 1 (DG);
038 * 21-Jan-2005 : Modified return type of RectangleAnchor.coordinates()
039 * method (DG);
040 * 20-Apr-2005 : Added new draw() method (DG);
041 * 13-May-2005 : Fixed to respect margin, border and padding settings (DG);
042 * 01-Sep-2005 : Implemented PublicCloneable (DG);
043 * ------------- JFREECHART 1.0.x ---------------------------------------------
044 * 13-Dec-2006 : Added fillPaintTransformer attribute, so legend graphics can
045 * display gradient paint correctly, updated equals() and
046 * corrected clone() (DG);
047 * 01-Aug-2007 : Updated API docs (DG);
048 *
049 */
050
051 package org.jfree.chart.title;
052
053 import java.awt.GradientPaint;
054 import java.awt.Graphics2D;
055 import java.awt.Paint;
056 import java.awt.Shape;
057 import java.awt.Stroke;
058 import java.awt.geom.Point2D;
059 import java.awt.geom.Rectangle2D;
060 import java.io.IOException;
061 import java.io.ObjectInputStream;
062 import java.io.ObjectOutputStream;
063
064 import org.jfree.chart.block.AbstractBlock;
065 import org.jfree.chart.block.Block;
066 import org.jfree.chart.block.LengthConstraintType;
067 import org.jfree.chart.block.RectangleConstraint;
068 import org.jfree.io.SerialUtilities;
069 import org.jfree.ui.GradientPaintTransformer;
070 import org.jfree.ui.RectangleAnchor;
071 import org.jfree.ui.Size2D;
072 import org.jfree.ui.StandardGradientPaintTransformer;
073 import org.jfree.util.ObjectUtilities;
074 import org.jfree.util.PaintUtilities;
075 import org.jfree.util.PublicCloneable;
076 import org.jfree.util.ShapeUtilities;
077
078 /**
079 * The graphical item within a legend item.
080 */
081 public class LegendGraphic extends AbstractBlock
082 implements Block, PublicCloneable {
083
084 /** For serialization. */
085 static final long serialVersionUID = -1338791523854985009L;
086
087 /**
088 * A flag that controls whether or not the shape is visible - see also
089 * lineVisible.
090 */
091 private boolean shapeVisible;
092
093 /**
094 * The shape to display. To allow for accurate positioning, the center
095 * of the shape should be at (0, 0).
096 */
097 private transient Shape shape;
098
099 /**
100 * Defines the location within the block to which the shape will be aligned.
101 */
102 private RectangleAnchor shapeLocation;
103
104 /**
105 * Defines the point on the shape's bounding rectangle that will be
106 * aligned to the drawing location when the shape is rendered.
107 */
108 private RectangleAnchor shapeAnchor;
109
110 /** A flag that controls whether or not the shape is filled. */
111 private boolean shapeFilled;
112
113 /** The fill paint for the shape. */
114 private transient Paint fillPaint;
115
116 /**
117 * The fill paint transformer (used if the fillPaint is an instance of
118 * GradientPaint).
119 *
120 * @since 1.0.4
121 */
122 private GradientPaintTransformer fillPaintTransformer;
123
124 /** A flag that controls whether or not the shape outline is visible. */
125 private boolean shapeOutlineVisible;
126
127 /** The outline paint for the shape. */
128 private transient Paint outlinePaint;
129
130 /** The outline stroke for the shape. */
131 private transient Stroke outlineStroke;
132
133 /**
134 * A flag that controls whether or not the line is visible - see also
135 * shapeVisible.
136 */
137 private boolean lineVisible;
138
139 /** The line. */
140 private transient Shape line;
141
142 /** The line stroke. */
143 private transient Stroke lineStroke;
144
145 /** The line paint. */
146 private transient Paint linePaint;
147
148 /**
149 * Creates a new legend graphic.
150 *
151 * @param shape the shape (<code>null</code> not permitted).
152 * @param fillPaint the fill paint (<code>null</code> not permitted).
153 */
154 public LegendGraphic(Shape shape, Paint fillPaint) {
155 if (shape == null) {
156 throw new IllegalArgumentException("Null 'shape' argument.");
157 }
158 if (fillPaint == null) {
159 throw new IllegalArgumentException("Null 'fillPaint' argument.");
160 }
161 this.shapeVisible = true;
162 this.shape = shape;
163 this.shapeAnchor = RectangleAnchor.CENTER;
164 this.shapeLocation = RectangleAnchor.CENTER;
165 this.shapeFilled = true;
166 this.fillPaint = fillPaint;
167 this.fillPaintTransformer = new StandardGradientPaintTransformer();
168 setPadding(2.0, 2.0, 2.0, 2.0);
169 }
170
171 /**
172 * Returns a flag that controls whether or not the shape
173 * is visible.
174 *
175 * @return A boolean.
176 *
177 * @see #setShapeVisible(boolean)
178 */
179 public boolean isShapeVisible() {
180 return this.shapeVisible;
181 }
182
183 /**
184 * Sets a flag that controls whether or not the shape is
185 * visible.
186 *
187 * @param visible the flag.
188 *
189 * @see #isShapeVisible()
190 */
191 public void setShapeVisible(boolean visible) {
192 this.shapeVisible = visible;
193 }
194
195 /**
196 * Returns the shape.
197 *
198 * @return The shape.
199 *
200 * @see #setShape(Shape)
201 */
202 public Shape getShape() {
203 return this.shape;
204 }
205
206 /**
207 * Sets the shape.
208 *
209 * @param shape the shape.
210 *
211 * @see #getShape()
212 */
213 public void setShape(Shape shape) {
214 this.shape = shape;
215 }
216
217 /**
218 * Returns a flag that controls whether or not the shapes
219 * are filled.
220 *
221 * @return A boolean.
222 *
223 * @see #setShapeFilled(boolean)
224 */
225 public boolean isShapeFilled() {
226 return this.shapeFilled;
227 }
228
229 /**
230 * Sets a flag that controls whether or not the shape is
231 * filled.
232 *
233 * @param filled the flag.
234 *
235 * @see #isShapeFilled()
236 */
237 public void setShapeFilled(boolean filled) {
238 this.shapeFilled = filled;
239 }
240
241 /**
242 * Returns the paint used to fill the shape.
243 *
244 * @return The fill paint.
245 *
246 * @see #setFillPaint(Paint)
247 */
248 public Paint getFillPaint() {
249 return this.fillPaint;
250 }
251
252 /**
253 * Sets the paint used to fill the shape.
254 *
255 * @param paint the paint.
256 *
257 * @see #getFillPaint()
258 */
259 public void setFillPaint(Paint paint) {
260 this.fillPaint = paint;
261 }
262
263 /**
264 * Returns the transformer used when the fill paint is an instance of
265 * <code>GradientPaint</code>.
266 *
267 * @return The transformer (never <code>null</code>).
268 *
269 * @since 1.0.4.
270 *
271 * @see #setFillPaintTransformer(GradientPaintTransformer)
272 */
273 public GradientPaintTransformer getFillPaintTransformer() {
274 return this.fillPaintTransformer;
275 }
276
277 /**
278 * Sets the transformer used when the fill paint is an instance of
279 * <code>GradientPaint</code>.
280 *
281 * @param transformer the transformer (<code>null</code> not permitted).
282 *
283 * @since 1.0.4
284 *
285 * @see #getFillPaintTransformer()
286 */
287 public void setFillPaintTransformer(GradientPaintTransformer transformer) {
288 if (transformer == null) {
289 throw new IllegalArgumentException("Null 'transformer' argument.");
290 }
291 this.fillPaintTransformer = transformer;
292 }
293
294 /**
295 * Returns a flag that controls whether the shape outline is visible.
296 *
297 * @return A boolean.
298 *
299 * @see #setShapeOutlineVisible(boolean)
300 */
301 public boolean isShapeOutlineVisible() {
302 return this.shapeOutlineVisible;
303 }
304
305 /**
306 * Sets a flag that controls whether or not the shape outline
307 * is visible.
308 *
309 * @param visible the flag.
310 *
311 * @see #isShapeOutlineVisible()
312 */
313 public void setShapeOutlineVisible(boolean visible) {
314 this.shapeOutlineVisible = visible;
315 }
316
317 /**
318 * Returns the outline paint.
319 *
320 * @return The paint.
321 *
322 * @see #setOutlinePaint(Paint)
323 */
324 public Paint getOutlinePaint() {
325 return this.outlinePaint;
326 }
327
328 /**
329 * Sets the outline paint.
330 *
331 * @param paint the paint.
332 *
333 * @see #getOutlinePaint()
334 */
335 public void setOutlinePaint(Paint paint) {
336 this.outlinePaint = paint;
337 }
338
339 /**
340 * Returns the outline stroke.
341 *
342 * @return The stroke.
343 *
344 * @see #setOutlineStroke(Stroke)
345 */
346 public Stroke getOutlineStroke() {
347 return this.outlineStroke;
348 }
349
350 /**
351 * Sets the outline stroke.
352 *
353 * @param stroke the stroke.
354 *
355 * @see #getOutlineStroke()
356 */
357 public void setOutlineStroke(Stroke stroke) {
358 this.outlineStroke = stroke;
359 }
360
361 /**
362 * Returns the shape anchor.
363 *
364 * @return The shape anchor.
365 *
366 * @see #getShapeAnchor()
367 */
368 public RectangleAnchor getShapeAnchor() {
369 return this.shapeAnchor;
370 }
371
372 /**
373 * Sets the shape anchor. This defines a point on the shapes bounding
374 * rectangle that will be used to align the shape to a location.
375 *
376 * @param anchor the anchor (<code>null</code> not permitted).
377 *
378 * @see #setShapeAnchor(RectangleAnchor)
379 */
380 public void setShapeAnchor(RectangleAnchor anchor) {
381 if (anchor == null) {
382 throw new IllegalArgumentException("Null 'anchor' argument.");
383 }
384 this.shapeAnchor = anchor;
385 }
386
387 /**
388 * Returns the shape location.
389 *
390 * @return The shape location.
391 *
392 * @see #setShapeLocation(RectangleAnchor)
393 */
394 public RectangleAnchor getShapeLocation() {
395 return this.shapeLocation;
396 }
397
398 /**
399 * Sets the shape location. This defines a point within the drawing
400 * area that will be used to align the shape to.
401 *
402 * @param location the location (<code>null</code> not permitted).
403 *
404 * @see #getShapeLocation()
405 */
406 public void setShapeLocation(RectangleAnchor location) {
407 if (location == null) {
408 throw new IllegalArgumentException("Null 'location' argument.");
409 }
410 this.shapeLocation = location;
411 }
412
413 /**
414 * Returns the flag that controls whether or not the line is visible.
415 *
416 * @return A boolean.
417 *
418 * @see #setLineVisible(boolean)
419 */
420 public boolean isLineVisible() {
421 return this.lineVisible;
422 }
423
424 /**
425 * Sets the flag that controls whether or not the line is visible.
426 *
427 * @param visible the flag.
428 *
429 * @see #isLineVisible()
430 */
431 public void setLineVisible(boolean visible) {
432 this.lineVisible = visible;
433 }
434
435 /**
436 * Returns the line centered about (0, 0).
437 *
438 * @return The line.
439 *
440 * @see #setLine(Shape)
441 */
442 public Shape getLine() {
443 return this.line;
444 }
445
446 /**
447 * Sets the line. A Shape is used here, because then you can use Line2D,
448 * GeneralPath or any other Shape to represent the line.
449 *
450 * @param line the line.
451 *
452 * @see #getLine()
453 */
454 public void setLine(Shape line) {
455 this.line = line;
456 }
457
458 /**
459 * Returns the line paint.
460 *
461 * @return The paint.
462 *
463 * @see #setLinePaint(Paint)
464 */
465 public Paint getLinePaint() {
466 return this.linePaint;
467 }
468
469 /**
470 * Sets the line paint.
471 *
472 * @param paint the paint.
473 *
474 * @see #getLinePaint()
475 */
476 public void setLinePaint(Paint paint) {
477 this.linePaint = paint;
478 }
479
480 /**
481 * Returns the line stroke.
482 *
483 * @return The stroke.
484 *
485 * @see #setLineStroke(Stroke)
486 */
487 public Stroke getLineStroke() {
488 return this.lineStroke;
489 }
490
491 /**
492 * Sets the line stroke.
493 *
494 * @param stroke the stroke.
495 *
496 * @see #getLineStroke()
497 */
498 public void setLineStroke(Stroke stroke) {
499 this.lineStroke = stroke;
500 }
501
502 /**
503 * Arranges the contents of the block, within the given constraints, and
504 * returns the block size.
505 *
506 * @param g2 the graphics device.
507 * @param constraint the constraint (<code>null</code> not permitted).
508 *
509 * @return The block size (in Java2D units, never <code>null</code>).
510 */
511 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
512 RectangleConstraint contentConstraint = toContentConstraint(constraint);
513 LengthConstraintType w = contentConstraint.getWidthConstraintType();
514 LengthConstraintType h = contentConstraint.getHeightConstraintType();
515 Size2D contentSize = null;
516 if (w == LengthConstraintType.NONE) {
517 if (h == LengthConstraintType.NONE) {
518 contentSize = arrangeNN(g2);
519 }
520 else if (h == LengthConstraintType.RANGE) {
521 throw new RuntimeException("Not yet implemented.");
522 }
523 else if (h == LengthConstraintType.FIXED) {
524 throw new RuntimeException("Not yet implemented.");
525 }
526 }
527 else if (w == LengthConstraintType.RANGE) {
528 if (h == LengthConstraintType.NONE) {
529 throw new RuntimeException("Not yet implemented.");
530 }
531 else if (h == LengthConstraintType.RANGE) {
532 throw new RuntimeException("Not yet implemented.");
533 }
534 else if (h == LengthConstraintType.FIXED) {
535 throw new RuntimeException("Not yet implemented.");
536 }
537 }
538 else if (w == LengthConstraintType.FIXED) {
539 if (h == LengthConstraintType.NONE) {
540 throw new RuntimeException("Not yet implemented.");
541 }
542 else if (h == LengthConstraintType.RANGE) {
543 throw new RuntimeException("Not yet implemented.");
544 }
545 else if (h == LengthConstraintType.FIXED) {
546 contentSize = new Size2D(
547 contentConstraint.getWidth(),
548 contentConstraint.getHeight()
549 );
550 }
551 }
552 return new Size2D(
553 calculateTotalWidth(contentSize.getWidth()),
554 calculateTotalHeight(contentSize.getHeight())
555 );
556 }
557
558 /**
559 * Performs the layout with no constraint, so the content size is
560 * determined by the bounds of the shape and/or line drawn to represent
561 * the series.
562 *
563 * @param g2 the graphics device.
564 *
565 * @return The content size.
566 */
567 protected Size2D arrangeNN(Graphics2D g2) {
568 Rectangle2D contentSize = new Rectangle2D.Double();
569 if (this.line != null) {
570 contentSize.setRect(this.line.getBounds2D());
571 }
572 if (this.shape != null) {
573 contentSize = contentSize.createUnion(this.shape.getBounds2D());
574 }
575 return new Size2D(contentSize.getWidth(), contentSize.getHeight());
576 }
577
578 /**
579 * Draws the graphic item within the specified area.
580 *
581 * @param g2 the graphics device.
582 * @param area the area.
583 */
584 public void draw(Graphics2D g2, Rectangle2D area) {
585
586 area = trimMargin(area);
587 drawBorder(g2, area);
588 area = trimBorder(area);
589 area = trimPadding(area);
590
591 if (this.lineVisible) {
592 Point2D location = RectangleAnchor.coordinates(area,
593 this.shapeLocation);
594 Shape aLine = ShapeUtilities.createTranslatedShape(getLine(),
595 this.shapeAnchor, location.getX(), location.getY());
596 g2.setPaint(this.linePaint);
597 g2.setStroke(this.lineStroke);
598 g2.draw(aLine);
599 }
600
601 if (this.shapeVisible) {
602 Point2D location = RectangleAnchor.coordinates(area,
603 this.shapeLocation);
604
605 Shape s = ShapeUtilities.createTranslatedShape(this.shape,
606 this.shapeAnchor, location.getX(), location.getY());
607 if (this.shapeFilled) {
608 Paint p = this.fillPaint;
609 if (p instanceof GradientPaint) {
610 GradientPaint gp = (GradientPaint) this.fillPaint;
611 p = this.fillPaintTransformer.transform(gp, s);
612 }
613 g2.setPaint(p);
614 g2.fill(s);
615 }
616 if (this.shapeOutlineVisible) {
617 g2.setPaint(this.outlinePaint);
618 g2.setStroke(this.outlineStroke);
619 g2.draw(s);
620 }
621 }
622
623 }
624
625 /**
626 * Draws the block within the specified area.
627 *
628 * @param g2 the graphics device.
629 * @param area the area.
630 * @param params ignored (<code>null</code> permitted).
631 *
632 * @return Always <code>null</code>.
633 */
634 public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
635 draw(g2, area);
636 return null;
637 }
638
639 /**
640 * Tests this <code>LegendGraphic</code> instance for equality with an
641 * arbitrary object.
642 *
643 * @param obj the object (<code>null</code> permitted).
644 *
645 * @return A boolean.
646 */
647 public boolean equals(Object obj) {
648 if (!(obj instanceof LegendGraphic)) {
649 return false;
650 }
651 LegendGraphic that = (LegendGraphic) obj;
652 if (this.shapeVisible != that.shapeVisible) {
653 return false;
654 }
655 if (!ShapeUtilities.equal(this.shape, that.shape)) {
656 return false;
657 }
658 if (this.shapeFilled != that.shapeFilled) {
659 return false;
660 }
661 if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
662 return false;
663 }
664 if (!ObjectUtilities.equal(this.fillPaintTransformer,
665 that.fillPaintTransformer)) {
666 return false;
667 }
668 if (this.shapeOutlineVisible != that.shapeOutlineVisible) {
669 return false;
670 }
671 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
672 return false;
673 }
674 if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
675 return false;
676 }
677 if (this.shapeAnchor != that.shapeAnchor) {
678 return false;
679 }
680 if (this.shapeLocation != that.shapeLocation) {
681 return false;
682 }
683 if (this.lineVisible != that.lineVisible) {
684 return false;
685 }
686 if (!ShapeUtilities.equal(this.line, that.line)) {
687 return false;
688 }
689 if (!PaintUtilities.equal(this.linePaint, that.linePaint)) {
690 return false;
691 }
692 if (!ObjectUtilities.equal(this.lineStroke, that.lineStroke)) {
693 return false;
694 }
695 return super.equals(obj);
696 }
697
698 /**
699 * Returns a hash code for this instance.
700 *
701 * @return A hash code.
702 */
703 public int hashCode() {
704 int result = 193;
705 result = 37 * result + ObjectUtilities.hashCode(this.fillPaint);
706 // FIXME: use other fields too
707 return result;
708 }
709
710 /**
711 * Returns a clone of this <code>LegendGraphic</code> instance.
712 *
713 * @return A clone of this <code>LegendGraphic</code> instance.
714 *
715 * @throws CloneNotSupportedException if there is a problem cloning.
716 */
717 public Object clone() throws CloneNotSupportedException {
718 LegendGraphic clone = (LegendGraphic) super.clone();
719 clone.shape = ShapeUtilities.clone(this.shape);
720 clone.line = ShapeUtilities.clone(this.line);
721 return clone;
722 }
723
724 /**
725 * Provides serialization support.
726 *
727 * @param stream the output stream.
728 *
729 * @throws IOException if there is an I/O error.
730 */
731 private void writeObject(ObjectOutputStream stream) throws IOException {
732 stream.defaultWriteObject();
733 SerialUtilities.writeShape(this.shape, stream);
734 SerialUtilities.writePaint(this.fillPaint, stream);
735 SerialUtilities.writePaint(this.outlinePaint, stream);
736 SerialUtilities.writeStroke(this.outlineStroke, stream);
737 SerialUtilities.writeShape(this.line, stream);
738 SerialUtilities.writePaint(this.linePaint, stream);
739 SerialUtilities.writeStroke(this.lineStroke, stream);
740 }
741
742 /**
743 * Provides serialization support.
744 *
745 * @param stream the input stream.
746 *
747 * @throws IOException if there is an I/O error.
748 * @throws ClassNotFoundException if there is a classpath problem.
749 */
750 private void readObject(ObjectInputStream stream)
751 throws IOException, ClassNotFoundException {
752 stream.defaultReadObject();
753 this.shape = SerialUtilities.readShape(stream);
754 this.fillPaint = SerialUtilities.readPaint(stream);
755 this.outlinePaint = SerialUtilities.readPaint(stream);
756 this.outlineStroke = SerialUtilities.readStroke(stream);
757 this.line = SerialUtilities.readShape(stream);
758 this.linePaint = SerialUtilities.readPaint(stream);
759 this.lineStroke = SerialUtilities.readStroke(stream);
760 }
761
762 }