KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > DefaultOldLegend


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this library; if not, write to the Free Software Foundation,
21  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
24  * in the United States and other countries.]
25  *
26  * ---------------------
27  * DefaultOldLegend.java
28  * ---------------------
29  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
30  *
31  * Original Author: David Gilbert (for Object Refinery Limited);
32  * Contributor(s): Andrzej Porebski;
33  * Luke Quinane;
34  * Barak Naveh;
35  *
36  * $Id: DefaultOldLegend.java,v 1.5 2005/05/19 15:40:56 mungady Exp $
37  *
38  * Changes (from 20-Jun-2001)
39  * --------------------------
40  * 20-Jun-2001 : Modifications submitted by Andrzej Porebski for legend
41  * placement;
42  * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG);
43  * 16-Oct-2001 : Moved data source classes to com.jrefinery.data.* (DG);
44  * 19-Oct-2001 : Moved some methods [getSeriesPaint() etc.] from JFreeChart to
45  * Plot (DG);
46  * 22-Jan-2002 : Fixed bug correlating legend labels with pie data (DG);
47  * 06-Feb-2002 : Bug fix for legends in small areas (DG);
48  * 23-Apr-2002 : Legend item labels are now obtained from the plot, not the
49  * chart (DG);
50  * 20-Jun-2002 : Added outline paint and stroke attributes for the key
51  * boxes (DG);
52  * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
53  * 23-Sep-2002 : Changed the name of LegendItem --> DrawableLegendItem (DG);
54  * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
55  * 16-Oct-2002 : Adjusted vertical text position in legend item (DG);
56  * 17-Oct-2002 : Fixed bug where legend items are not using the font that has
57  * been set (DG);
58  * 11-Feb-2003 : Added title code by Donald Mitchell, removed unnecessary
59  * constructor (DG);
60  * 26-Mar-2003 : Implemented Serializable (DG);
61  * 22-Sep-2003 : Added nullpointer checks (TM);
62  * 23-Sep-2003 : Fixed bug in equals() method (DG);
63  * 08-Oct-2003 : Applied patch for displaying series line style, contributed
64  * by Luke Quinane (DG);
65  * 23-Dec-2003 : Added scale factors (x and y) for shapes displayed in
66  * legend (DG);
67  * 26-Mar-2004 : Added option to control item order, contributed by Angel (DG);
68  * 26-Mar-2004 : Added support for 8 more anchor points (BN);
69  * 27-Mar-2004 : Added support for round corners of bounding box (BN);
70  * 07-Apr-2004 : Changed text bounds calculation (DG);
71  * 21-Apr-2004 : Barak Naveh has contributed word-wrapping for legend
72  * items (BN);
73  * 25-Nov-2004 : Small update due to changes in LegendItem class (DG);
74  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
75  * 27-Jan-2005 : Legend now picks up more info from the renderer of each
76  * series (DG);
77  * 22-Feb-2005 : Renamed outerGap --> margin (DG);
78  * 28-Mar-2005 : Renamed StandardLegend --> DefaultOldLegend (DG);
79  * 20-Apr-2005 : Updated for changes in LegendItem (DG);
80  * 26-Apr-2005 : Removed LOGGER (DG);
81  *
82  */

83
84 package org.jfree.chart;
85
86 import java.awt.BasicStroke JavaDoc;
87 import java.awt.Color JavaDoc;
88 import java.awt.Font JavaDoc;
89 import java.awt.FontMetrics JavaDoc;
90 import java.awt.Graphics2D JavaDoc;
91 import java.awt.Paint JavaDoc;
92 import java.awt.Shape JavaDoc;
93 import java.awt.Stroke JavaDoc;
94 import java.awt.font.LineMetrics JavaDoc;
95 import java.awt.geom.AffineTransform JavaDoc;
96 import java.awt.geom.Line2D JavaDoc;
97 import java.awt.geom.Point2D JavaDoc;
98 import java.awt.geom.Rectangle2D JavaDoc;
99 import java.awt.geom.RectangularShape JavaDoc;
100 import java.awt.geom.RoundRectangle2D JavaDoc;
101 import java.io.IOException JavaDoc;
102 import java.io.ObjectInputStream JavaDoc;
103 import java.io.ObjectOutputStream JavaDoc;
104 import java.io.Serializable JavaDoc;
105 import java.util.ArrayList JavaDoc;
106 import java.util.Iterator JavaDoc;
107 import java.util.List JavaDoc;
108
109 import org.jfree.chart.entity.EntityCollection;
110 import org.jfree.chart.entity.LegendItemEntity;
111 import org.jfree.chart.event.LegendChangeEvent;
112 import org.jfree.io.SerialUtilities;
113 import org.jfree.text.TextUtilities;
114 import org.jfree.ui.RectangleInsets;
115 import org.jfree.ui.TextAnchor;
116 import org.jfree.util.ObjectUtilities;
117
118 /**
119  * A chart legend shows the names and visual representations of the series
120  * that are plotted in a chart. If possible, use
121  * {@link org.jfree.chart.title.LegendTitle} rather than this class.
122  */

123 public class DefaultOldLegend extends OldLegend implements Serializable JavaDoc {
124
125     /** For serialization. */
126     private static final long serialVersionUID = -5466149184220837922L;
127     
128     /** The default margin (space around the outside of the legend). */
129     public static final RectangleInsets DEFAULT_MARGIN
130         = new RectangleInsets(3, 3, 3, 3);
131
132     /** The default inner gap. */
133     public static final RectangleInsets DEFAULT_PADDING
134         = new RectangleInsets(2, 2, 2, 2);
135
136     /** The default outline stroke. */
137     public static final Stroke JavaDoc DEFAULT_OUTLINE_STROKE = new BasicStroke JavaDoc();
138
139     /** The default outline paint. */
140     public static final Paint JavaDoc DEFAULT_OUTLINE_PAINT = Color.gray;
141
142     /** The default background paint. */
143     public static final Paint JavaDoc DEFAULT_BACKGROUND_PAINT = Color.white;
144
145     /** The default title font. */
146     public static final Font JavaDoc DEFAULT_TITLE_FONT
147         = new Font JavaDoc("SansSerif", Font.BOLD, 11);
148
149     /** The default item font. */
150     public static final Font JavaDoc DEFAULT_ITEM_FONT
151         = new Font JavaDoc("SansSerif", Font.PLAIN, 10);
152
153     /**
154      * Used with {@link #setPreferredWidth(double)} to indicate that no
155      * preferred width is desired and defaults are to be used.
156      */

157     public static final double NO_PREFERRED_WIDTH = Double.MAX_VALUE;
158     
159     /** Reported when illegal legend is unexpectedly found. */
160     private static final String JavaDoc UNEXPECTED_LEGEND_ANCHOR
161         = "Unexpected legend anchor";
162     
163     /** The amount of blank space around the legend. */
164     private RectangleInsets margin;
165
166     /** The stroke used to draw the outline of the legend. */
167     private transient Stroke JavaDoc outlineStroke;
168
169     /** The paint used to draw the outline of the legend. */
170     private transient Paint JavaDoc outlinePaint;
171
172     /** The paint used to draw the background of the legend. */
173     private transient Paint JavaDoc backgroundPaint;
174
175     /** The blank space inside the legend box. */
176     private RectangleInsets padding;
177
178     /** An optional title for the legend. */
179     private String JavaDoc title;
180
181     /** The font used to display the legend title. */
182     private Font JavaDoc titleFont;
183
184     /** The font used to display the legend item names. */
185     private Font JavaDoc itemFont;
186
187     /** The paint used to display the legend item names. */
188     private transient Paint JavaDoc itemPaint;
189   
190     /** The x scale factor for shapes displayed in the legend. */
191     private double shapeScaleX = 1.0;
192     
193     /** The y scale factor for shapes displayed in the legend. */
194     private double shapeScaleY = 1.0;
195
196     /** The order of the legend items. */
197     private LegendRenderingOrder renderingOrder = LegendRenderingOrder.STANDARD;
198     
199     /**
200      * The width of the arc used to round off the corners of the bounding box.
201      */

202     private int boundingBoxArcWidth = 0;
203
204     /**
205      * The height of the arc used to round off the corners of the bounding box.
206      */

207     private int boundingBoxArcHeight = 0;
208
209     /** The preferred width of the legend bounding box. */
210     private double preferredWidth = NO_PREFERRED_WIDTH;
211     
212     /**
213      * Constructs a new legend with default settings.
214      */

215     public DefaultOldLegend() {
216         this.margin = DEFAULT_MARGIN;
217         this.padding = DEFAULT_PADDING;
218         this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
219         this.outlineStroke = DEFAULT_OUTLINE_STROKE;
220         this.outlinePaint = DEFAULT_OUTLINE_PAINT;
221         this.title = null;
222         this.titleFont = DEFAULT_TITLE_FONT;
223         this.itemFont = DEFAULT_ITEM_FONT;
224         this.itemPaint = Color.black;
225     }
226
227     /**
228      * Returns the margin for the legend. This is the amount of blank space
229      * around the outside of the legend.
230      *
231      * @return The gap (never <code>null</code>).
232      */

233     public RectangleInsets getMargin() {
234         return this.margin;
235     }
236
237     /**
238      * Sets the margin for the legend and sends a {@link LegendChangeEvent} to
239      * all registered listeners.
240      *
241      * @param margin the margin (<code>null</code> not permitted).
242      */

243     public void setMargin(RectangleInsets margin) {
244         if (margin == null) {
245             throw new NullPointerException JavaDoc("Null 'margin' argument.");
246         }
247         this.margin = margin;
248         notifyListeners(new LegendChangeEvent(this));
249     }
250
251     /**
252      * Returns the padding for the legend. This is the amount of blank space
253      * around the inside of the legend.
254      *
255      * @return The gap (never <code>null</code>).
256      */

257     public RectangleInsets getPadding() {
258         return this.padding;
259     }
260
261     /**
262      * Sets the padding for the legend and sends a {@link LegendChangeEvent}
263      * to all registered listeners.
264      *
265      * @param padding the padding (<code>null</code> not permitted).
266      */

267     public void setPadding(RectangleInsets padding) {
268         if (padding == null) {
269             throw new NullPointerException JavaDoc("Null 'padding' argument.");
270         }
271         this.padding = padding;
272         notifyListeners(new LegendChangeEvent(this));
273     }
274
275     /**
276      * Returns the background paint for the legend.
277      *
278      * @return The background paint (never <code>null</code>).
279      */

280     public Paint JavaDoc getBackgroundPaint() {
281         return this.backgroundPaint;
282     }
283
284     /**
285      * Sets the background paint for the legend and sends a
286      * {@link LegendChangeEvent} to all registered listeners.
287      *
288      * @param paint the paint (<code>null</code> not permitted).
289      */

290     public void setBackgroundPaint(Paint JavaDoc paint) {
291         if (paint == null) {
292             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
293         }
294         this.backgroundPaint = paint;
295         notifyListeners(new LegendChangeEvent(this));
296     }
297
298     /**
299      * Returns the outline stroke.
300      *
301      * @return The outline stroke (never <code>null</code>).
302      */

303     public Stroke JavaDoc getOutlineStroke() {
304         return this.outlineStroke;
305     }
306
307     /**
308      * Sets the outline stroke and sends a {@link LegendChangeEvent} to all
309      * registered listeners.
310      *
311      * @param stroke the stroke (<code>null</code> not permitted).
312      */

313     public void setOutlineStroke(Stroke JavaDoc stroke) {
314         if (stroke == null) {
315             throw new NullPointerException JavaDoc("Null 'stroke' argument.");
316         }
317         this.outlineStroke = stroke;
318         notifyListeners(new LegendChangeEvent(this));
319     }
320
321     /**
322      * Returns the outline paint.
323      *
324      * @return The outline paint (never <code>null</code>).
325      */

326     public Paint JavaDoc getOutlinePaint() {
327         return this.outlinePaint;
328     }
329
330     /**
331      * Sets the outline paint and sends a {@link LegendChangeEvent} to all
332      * registered listeners.
333      *
334      * @param paint the paint (<code>null</code> not permitted).
335      */

336     public void setOutlinePaint(Paint JavaDoc paint) {
337         if (paint == null) {
338             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
339         }
340         this.outlinePaint = paint;
341         notifyListeners(new LegendChangeEvent(this));
342     }
343
344     /**
345      * Gets the title for the legend.
346      *
347      * @return The title (possibly <code>null</code>).
348      */

349     public String JavaDoc getTitle() {
350         return this.title;
351     }
352
353     /**
354      * Sets the title of the legend and sends a {@link LegendChangeEvent} to
355      * all registered listeners.
356      *
357      * @param title the title (<code>null</code> permitted).
358      */

359     public void setTitle(String JavaDoc title) {
360         this.title = title;
361         notifyListeners(new LegendChangeEvent(this));
362     }
363
364     /**
365      * Returns the title font.
366      *
367      * @return The font (never <code>null</code>).
368      */

369     public Font JavaDoc getTitleFont() {
370         return this.titleFont;
371     }
372
373     /**
374      * Sets the title font and sends a {@link LegendChangeEvent} to all
375      * registered listeners.
376      *
377      * @param font the font (<code>null</code> not permitted).
378      */

379     public void setTitleFont(Font JavaDoc font) {
380         if (font == null) {
381             throw new IllegalArgumentException JavaDoc("Null 'font' argument.");
382         }
383         this.titleFont = font;
384         notifyListeners(new LegendChangeEvent(this));
385     }
386
387     /**
388      * Returns the series label font.
389      *
390      * @return The font (never <code>null</code>).
391      */

392     public Font JavaDoc getItemFont() {
393         return this.itemFont;
394     }
395
396     /**
397      * Sets the series label font and sends a {@link LegendChangeEvent} to all
398      * registered listeners.
399      *
400      * @param font the font (<code>null</code> not permitted).
401      */

402     public void setItemFont(Font JavaDoc font) {
403         if (font == null) {
404             throw new IllegalArgumentException JavaDoc("Null 'font' argument.");
405         }
406         this.itemFont = font;
407         notifyListeners(new LegendChangeEvent(this));
408     }
409
410     /**
411      * Returns the series label paint.
412      *
413      * @return The paint (never <code>null</code>).
414      */

415     public Paint JavaDoc getItemPaint() {
416         return this.itemPaint;
417     }
418
419     /**
420      * Sets the series label paint and sends a {@link LegendChangeEvent} to all
421      * registered listeners.
422      *
423      * @param paint the paint (<code>null</code> not permitted).
424      */

425     public void setItemPaint(Paint JavaDoc paint) {
426         if (paint == null) {
427             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
428         }
429         this.itemPaint = paint;
430         notifyListeners(new LegendChangeEvent(this));
431     }
432
433     /**
434      * Returns the x scale factor for shapes displayed in the legend.
435      *
436      * @return The x scale factor.
437      */

438     public double getShapeScaleX() {
439         return this.shapeScaleX;
440     }
441     
442     /**
443      * Sets the x scale factor for shapes displayed in the legend and sends a
444      * {@link LegendChangeEvent} to all registered listeners.
445      *
446      * @param factor the factor.
447      */

448     public void setShapeScaleX(double factor) {
449         this.shapeScaleX = factor;
450         notifyListeners(new LegendChangeEvent(this));
451     }
452
453     /**
454      * Returns the y scale factor for shapes displayed in the legend.
455      *
456      * @return The y scale factor.
457      */

458     public double getShapeScaleY() {
459         return this.shapeScaleY;
460     }
461     
462     /**
463      * Sets the y scale factor for shapes displayed in the legend and sends a
464      * {@link LegendChangeEvent} to all registered listeners.
465      *
466      * @param factor the factor.
467      */

468     public void setShapeScaleY(double factor) {
469         this.shapeScaleY = factor;
470         notifyListeners(new LegendChangeEvent(this));
471     }
472
473     /**
474      * Returns the legend rendering order.
475      *
476      * @return The order (never <code>null</code>).
477      */

478     public LegendRenderingOrder getRenderingOrder() {
479         return this.renderingOrder;
480     }
481
482     /**
483      * Sets the legend rendering order and sends a {@link LegendChangeEvent} to
484      * all registered listeners.
485      *
486      * @param order the order (<code>null</code> not permitted).
487      */

488     public void setRenderingOrder(LegendRenderingOrder order) {
489         if (order == null) {
490             throw new IllegalArgumentException JavaDoc("Null 'order' argument.");
491         }
492         this.renderingOrder = order;
493         notifyListeners(new LegendChangeEvent(this));
494     }
495
496     
497     /**
498      * Returns the width of the arc used to round off the corners of the
499      * bounding box.
500      *
501      * @return The width of the arc used to round off the corners of the
502      * bounding box.
503      */

504     public int getBoundingBoxArcWidth() {
505         return this.boundingBoxArcWidth;
506     }
507     
508     /**
509      * Sets the width of the arc used to round off the corners of the
510      * bounding box.
511      * A {@link LegendChangeEvent} is sent to all registered listeners.
512      *
513      * @param arcWidth the new arc width.
514      */

515     public void setBoundingBoxArcWidth(int arcWidth) {
516         this.boundingBoxArcWidth = arcWidth;
517         notifyListeners(new LegendChangeEvent(this));
518     }
519     
520     /**
521      * Returns the height of the arc used to round off the corners of the
522      * bounding box.
523      *
524      * @return The height of the arc used to round off the corners of the
525      * bounding box.
526      */

527     public int getBoundingBoxArcHeight() {
528         return this.boundingBoxArcHeight;
529     }
530     
531     /**
532      * Sets the height of the arc used to round off the corners of the
533      * bounding box.
534      * A {@link LegendChangeEvent} is sent to all registered listeners.
535      *
536      * @param arcHeight the new arc height.
537      */

538     public void setBoundingBoxArcHeight(int arcHeight) {
539         this.boundingBoxArcHeight = arcHeight;
540         notifyListeners(new LegendChangeEvent(this));
541     }
542
543     /**
544      * Returns the preferred width of the legend bounding box if such width
545      * has been defined; otherwise returns <code>NO_PREFERRED_WIDTH</code>.
546      *
547      * @return The preferred width of the legend bounding box if such width
548      * has been defined; otherwise returns
549      * <code>NO_PREFERRED_WIDTH</code>.
550      */

551     public double getPreferredWidth() {
552         return this.preferredWidth;
553     }
554
555     /**
556      * Sets the preferred width of the legend bounding box. If a preferred
557      * width is set, the legend text is word-wrapped in an attempt to fulfill
558      * the preferred width. If the preferred width cannot be fulfilled, the
559      * legend would be wider to the extent necessary.
560      *
561      * <p>
562      * The preferred width takes effect only when the legend's anchor is set to
563      * one of the three EAST anchors or to one of the three WEST anchors.
564      *
565      * <p>
566      * A {@link LegendChangeEvent} is sent to all registered listeners.
567      *
568      * @param width the new width.
569      */

570     public void setPreferredWidth(double width) {
571         this.preferredWidth = width;
572         notifyListeners(new LegendChangeEvent(this));
573     }
574     
575     /**
576      * Draws the legend on a Java 2D graphics device (such as the screen or a
577      * printer).
578      *
579      * @param g2 the graphics device.
580      * @param available the area within which the legend, and afterwards the
581      * plot, should be drawn.
582      * @param info collects rendering info (optional).
583      *
584      * @return The area NOT used by the legend.
585      */

586     public Rectangle2D JavaDoc draw(Graphics2D JavaDoc g2, Rectangle2D JavaDoc available,
587                             ChartRenderingInfo info) {
588
589         return draw(
590             g2, available, (getAnchor() & HORIZONTAL) != 0,
591             (getAnchor() & INVERTED) != 0, info
592         );
593
594     }
595
596     /**
597      * Draws the legend.
598      *
599      * @param g2 the graphics device.
600      * @param available the area available for drawing the chart.
601      * @param horizontal a flag indicating whether the legend items are laid
602      * out horizontally.
603      * @param inverted ???
604      * @param info collects rendering info (optional).
605      *
606      * @return The remaining available drawing area.
607      */

608     protected Rectangle2D JavaDoc draw(Graphics2D JavaDoc g2, Rectangle2D JavaDoc available,
609                                boolean horizontal, boolean inverted,
610                                ChartRenderingInfo info) {
611
612         LegendItemCollection legendItems
613             = getChart().getPlot().getLegendItems();
614
615         if (legendItems == null || legendItems.getItemCount() == 0) {
616             return available;
617         }
618         // else...
619

620         DrawableLegendItem legendTitle = null;
621         LegendItem titleItem = null;
622
623         if (this.title != null && !this.title.equals("")) {
624             titleItem = new LegendItem(
625                 this.title, this.title, null, null, (Shape JavaDoc) null, Color.black
626             );
627         }
628
629         RectangularShape JavaDoc legendArea;
630         double availableWidth = available.getWidth();
631         // the translation point for the origin of the drawing system
632
Point2D JavaDoc translation;
633
634         // Create buffer for individual items within the legend
635
List JavaDoc items = new ArrayList JavaDoc();
636
637         // Compute individual rectangles in the legend, translation point as
638
// well as the bounding box for the legend.
639
if (horizontal) {
640             double xstart = available.getX()
641                             + getMargin().calculateLeftOutset(availableWidth);
642             double xlimit = available.getMaxX()
643                             - getMargin().calculateRightOutset(availableWidth);
644             double maxRowWidth = 0;
645             double xoffset = 0;
646             double rowHeight = 0;
647             double totalHeight = 0;
648             boolean wrappingAllowed = true;
649
650             if (titleItem != null) {
651                 g2.setFont(getTitleFont());
652
653                 legendTitle = createDrawableLegendItem(
654                     g2, titleItem, xoffset, totalHeight
655                 );
656
657                 rowHeight = Math.max(0, legendTitle.getHeight());
658                 xoffset += legendTitle.getWidth();
659             }
660
661             g2.setFont(this.itemFont);
662             for (int i = 0; i < legendItems.getItemCount(); i++) {
663                 DrawableLegendItem item;
664                 
665                 if (this.renderingOrder == LegendRenderingOrder.STANDARD) {
666                     item = createDrawableLegendItem(
667                         g2, legendItems.get(i), xoffset, totalHeight
668                     );
669                 }
670                 else if (this.renderingOrder == LegendRenderingOrder.REVERSE) {
671                     item = createDrawableLegendItem(
672                         g2, legendItems.get(legendItems.getItemCount() - i - 1),
673                         xoffset, totalHeight
674                     );
675                 }
676                 else {
677                     // we're not supposed to get here, will cause
678
// NullPointerException
679
item = null;
680                 }
681
682                 if (item.getMaxX() + xstart > xlimit && wrappingAllowed) {
683                     // start a new row
684
maxRowWidth = Math.max(maxRowWidth, xoffset);
685                     xoffset = 0;
686                     totalHeight += rowHeight;
687                     i--; // redo this item in the next row
688
// if item to big to fit, we dont want to attempt wrapping
689
// endlessly.
690
// we therefore disable wrapping for at least one item.
691
wrappingAllowed = false;
692                 }
693                 else {
694                     // continue current row
695
rowHeight = Math.max(rowHeight, item.getHeight());
696                     xoffset += item.getWidth();
697                     // we placed an item in this row, re-allow wrapping for
698
// next item.
699
wrappingAllowed = true;
700                     items.add(item);
701                 }
702             }
703
704             maxRowWidth = Math.max(maxRowWidth, xoffset);
705             totalHeight += rowHeight;
706
707             // Create the bounding box
708
legendArea = new RoundRectangle2D.Double JavaDoc(
709                 0, 0, maxRowWidth, totalHeight, this.boundingBoxArcWidth,
710                 this.boundingBoxArcHeight
711             );
712
713             translation = createTranslationPointForHorizontalDraw(
714                 available, inverted, maxRowWidth, totalHeight
715             );
716         }
717         else { // vertical...
718
double totalHeight = 0;
719             double maxWidth = (this.preferredWidth == NO_PREFERRED_WIDTH)
720                 ? 0 : this.preferredWidth;
721             
722             if (titleItem != null) {
723                 g2.setFont(getTitleFont());
724
725                 legendTitle = createDrawableLegendItem(
726                     g2, titleItem, 0, totalHeight
727                 );
728
729                 totalHeight += legendTitle.getHeight();
730                 maxWidth = Math.max(maxWidth, legendTitle.getWidth());
731             }
732
733             g2.setFont(this.itemFont);
734             
735             int legendItemsLength = legendItems.getItemCount();
736             for (int i = 0; i < legendItemsLength; i++) {
737                 List JavaDoc drawableParts;
738                 
739                 if (this.renderingOrder == LegendRenderingOrder.STANDARD) {
740                     drawableParts = createAllDrawableLinesForItem(g2,
741                             legendItems.get(i), 0, totalHeight, maxWidth);
742                 }
743                 else if (this.renderingOrder == LegendRenderingOrder.REVERSE) {
744                     drawableParts = createAllDrawableLinesForItem(
745                         g2, legendItems.get(legendItemsLength - i - 1), 0,
746                         totalHeight, maxWidth
747                     );
748                 }
749                 else {
750                     // we're not supposed to get here, will cause
751
// NullPointerException
752
drawableParts = null;
753                 }
754                 
755                 for (Iterator JavaDoc j = drawableParts.iterator(); j.hasNext();) {
756                     DrawableLegendItem item = (DrawableLegendItem) j.next();
757                     
758                     totalHeight += item.getHeight();
759                     maxWidth = Math.max(maxWidth, item.getWidth());
760                     
761                     items.add(item);
762                 }
763             }
764
765             // Create the bounding box
766
legendArea = new RoundRectangle2D.Float JavaDoc(
767                 0, 0, (float) maxWidth, (float) totalHeight,
768                 this.boundingBoxArcWidth, this.boundingBoxArcHeight
769             );
770
771             translation = createTranslationPointForVerticalDraw(
772                 available, inverted, totalHeight, maxWidth
773             );
774         }
775
776         // Move the origin of the drawing to the appropriate location
777
g2.translate(translation.getX(), translation.getY());
778         drawLegendBox(g2, legendArea);
779         drawLegendTitle(g2, legendTitle);
780         drawSeriesElements(g2, items, translation, info);
781
782         // translate the origin back to what it was prior to drawing the legend
783
g2.translate(-translation.getX(), -translation.getY());
784
785         return calcRemainingDrawingArea(
786             available, horizontal, inverted, legendArea
787         );
788     }
789
790     /**
791      * ???
792      *
793      * @param available the available area.
794      * @param inverted inverted?
795      * @param maxRowWidth the maximum row width.
796      * @param totalHeight the total height.
797      *
798      * @return The translation point.
799      */

800     private Point2D JavaDoc createTranslationPointForHorizontalDraw(
801             Rectangle2D JavaDoc available, boolean inverted, double maxRowWidth,
802             double totalHeight) {
803         // The yloc point is the variable part of the translation point
804
// for horizontal legends xloc can be: left, center or right.
805
double yloc = (inverted)
806             ? available.getMaxY() - totalHeight
807               - getMargin().calculateBottomOutset(available.getHeight())
808             : available.getY()
809               + getMargin().calculateTopOutset(available.getHeight());
810         double xloc;
811         if (isAnchoredToLeft()) {
812             xloc = available.getX()
813                    + getMargin().calculateLeftOutset(available.getWidth());
814         }
815         else if (isAnchoredToCenter()) {
816             xloc = available.getX() + available.getWidth() / 2
817                    - maxRowWidth / 2;
818         }
819         else if (isAnchoredToRight()) {
820             xloc = available.getX() + available.getWidth()
821                    - maxRowWidth - getChart().getPlot().getInsets().getLeft();
822         }
823         else {
824             throw new IllegalStateException JavaDoc(UNEXPECTED_LEGEND_ANCHOR);
825         }
826         
827         // Create the translation point
828
return new Point2D.Double JavaDoc(xloc, yloc);
829     }
830
831     /**
832      * ???
833      *
834      * @param available the available area.
835      * @param inverted inverted?
836      * @param totalHeight the total height.
837      * @param maxWidth the maximum width.
838      *
839      * @return The translation point.
840      */

841     private Point2D JavaDoc createTranslationPointForVerticalDraw(Rectangle2D JavaDoc available,
842             boolean inverted, double totalHeight, double maxWidth) {
843         // The xloc point is the variable part of the translation point
844
// for vertical legends yloc can be: top, middle or bottom.
845
double xloc = (inverted)
846             ? available.getMaxX() - maxWidth
847               - getMargin().calculateRightOutset(available.getWidth())
848             : available.getX()
849               + getMargin().calculateLeftOutset(available.getWidth());
850         double yloc;
851         if (isAnchoredToTop()) {
852             yloc = available.getY() + getChart().getPlot().getInsets().getTop();
853         }
854         else if (isAnchoredToMiddle()) {
855             yloc = available.getY() + (available.getHeight() / 2)
856                    - (totalHeight / 2);
857         }
858         else if (isAnchoredToBottom()) {
859             yloc = available.getY() + available.getHeight()
860                    - getChart().getPlot().getInsets().getBottom() - totalHeight;
861         }
862         else {
863             throw new IllegalStateException JavaDoc(UNEXPECTED_LEGEND_ANCHOR);
864         }
865         // Create the translation point
866
return new Point2D.Double JavaDoc(xloc, yloc);
867     }
868
869     /**
870      * Draws the legend title.
871      *
872      * @param g2 the graphics device (<code>null</code> not permitted).
873      * @param legendTitle the title (<code>null</code> permitted, in which
874      * case the method does nothing).
875      */

876     private void drawLegendTitle(Graphics2D JavaDoc g2,
877                                  DrawableLegendItem legendTitle) {
878         if (legendTitle != null) {
879             // XXX dsm - make title bold?
880
g2.setPaint(legendTitle.getItem().getFillPaint());
881             g2.setPaint(this.itemPaint);
882             g2.setFont(getTitleFont());
883             TextUtilities.drawAlignedString(
884                 legendTitle.getItem().getLabel(), g2,
885                 (float) legendTitle.getLabelPosition().getX(),
886                 (float) legendTitle.getLabelPosition().getY(),
887                 TextAnchor.CENTER_LEFT
888             );
889         }
890     }
891
892     /**
893      * Draws the bounding box for the legend.
894      *
895      * @param g2 the graphics device.
896      * @param legendArea the legend area.
897      */

898     private void drawLegendBox(Graphics2D JavaDoc g2, RectangularShape JavaDoc legendArea) {
899         // Draw the legend's bounding box
900
g2.setPaint(this.backgroundPaint);
901         g2.fill(legendArea);
902         g2.setPaint(this.outlinePaint);
903         g2.setStroke(this.outlineStroke);
904         g2.draw(legendArea);
905     }
906
907     /**
908      * Draws the series elements.
909      *
910      * @param g2 the graphics device.
911      * @param items the items.
912      * @param translation the translation point.
913      * @param info optional carrier for rendering info.
914      */

915     private void drawSeriesElements(Graphics2D JavaDoc g2, List JavaDoc items,
916                                     Point2D JavaDoc translation,
917                                     ChartRenderingInfo info) {
918         EntityCollection entities = null;
919         if (info != null) {
920             entities = info.getEntityCollection();
921         }
922         // Draw individual series elements
923
for (int i = 0; i < items.size(); i++) {
924             DrawableLegendItem item = (DrawableLegendItem) items.get(i);
925             g2.setPaint(item.getItem().getFillPaint());
926             Shape JavaDoc keyBox = item.getMarker();
927             if (item.getItem().isLineVisible()) {
928                 g2.setStroke(item.getItem().getLineStroke());
929                 g2.draw(item.getLine());
930
931                 if (item.getItem().isShapeVisible()) {
932                     if (item.getItem().isShapeFilled()) {
933                         g2.fill(keyBox);
934                     }
935                     else {
936                         g2.draw(keyBox);
937                     }
938                 }
939             }
940             else {
941                 if (item.getItem().isShapeFilled()) {
942                     g2.fill(keyBox);
943                 }
944                 if (item.getItem().isShapeOutlineVisible()) {
945                     g2.setPaint(item.getItem().getOutlinePaint());
946                     g2.setStroke(item.getItem().getOutlineStroke());
947                     g2.draw(keyBox);
948                 }
949             }
950             g2.setPaint(this.itemPaint);
951             g2.setFont(this.itemFont);
952             TextUtilities.drawAlignedString(
953                 item.getItem().getLabel(), g2,
954                 (float) item.getLabelPosition().getX(),
955                 (float) item.getLabelPosition().getY(),
956                 TextAnchor.CENTER_LEFT
957             );
958
959             if (entities != null) {
960                 Rectangle2D JavaDoc area = new Rectangle2D.Double JavaDoc(
961                     translation.getX() + item.getX(),
962                     translation.getY() + item.getY(),
963                     item.getWidth(), item.getHeight()
964                 );
965                 LegendItemEntity entity = new LegendItemEntity(area);
966                 entity.setSeriesIndex(i);
967                 entities.add(entity);
968             }
969         }
970     }
971
972     /**
973      * Calculates the remaining drawing area.
974      *
975      * @param available the available area.
976      * @param horizontal horizontal?
977      * @param inverted inverted?
978      * @param legendArea the legend area.
979      *
980      * @return The remaining drawing area.
981      */

982     private Rectangle2D JavaDoc calcRemainingDrawingArea(Rectangle2D JavaDoc available,
983             boolean horizontal, boolean inverted, RectangularShape JavaDoc legendArea) {
984         if (horizontal) {
985             // The remaining drawing area bounding box will have the same
986
// x origin, width and height independent of the anchor's
987
// location. The variable is the y coordinate. If the anchor is
988
// SOUTH, the y coordinate is simply the original y coordinate
989
// of the available area. If it is NORTH, we adjust original y
990
// by the total height of the legend and the initial gap.
991
double yy = available.getY();
992             double yloc = (inverted) ? yy
993                 : yy + legendArea.getHeight()
994                 + getMargin().calculateBottomOutset(available.getHeight());
995
996             // return the remaining available drawing area
997
return new Rectangle2D.Double JavaDoc(
998                 available.getX(), yloc, available.getWidth(),
999                 available.getHeight() - legendArea.getHeight()
1000                - getMargin().calculateTopOutset(available.getHeight())
1001                - getMargin().calculateBottomOutset(available.getHeight())
1002            );
1003        }
1004        else {
1005            // The remaining drawing area bounding box will have the same
1006
// y origin, width and height independent of the anchor's
1007
// location. The variable is the x coordinate. If the anchor is
1008
// EAST, the x coordinate is simply the original x coordinate
1009
// of the available area. If it is WEST, we adjust original x
1010
// by the total width of the legend and the initial gap.
1011
double xloc = (inverted) ? available.getX()
1012                : available.getX()
1013                + legendArea.getWidth()
1014                + getMargin().calculateLeftOutset(available.getWidth())
1015                + getMargin().calculateRightOutset(available.getWidth());
1016
1017
1018            // return the remaining available drawing area
1019
return new Rectangle2D.Double JavaDoc(
1020                xloc, available.getY(),
1021                available.getWidth() - legendArea.getWidth()
1022                - getMargin().calculateLeftOutset(available.getWidth())
1023                - getMargin().calculateRightOutset(available.getWidth()),
1024                available.getHeight()
1025            );
1026        }
1027    }
1028
1029    /**
1030     * Returns a list of drawable legend items for the specified legend item.
1031     * Word-wrapping is applied to the specified legend item and it is broken
1032     * into a few lines in order to fit into the specified
1033     * <code>wordWrapWidth</code>.
1034     *
1035     * @param g2 the graphics context.
1036     * @param legendItem the legend item.
1037     * @param x the upper left x coordinate for the bounding box.
1038     * @param y the upper left y coordinate for the bounding box.
1039     * @param wordWrapWidth the word wrap width.
1040     *
1041     * @return A list of drawable legend items for the specified legend item.
1042     *
1043     * @see #setPreferredWidth(double)
1044     */

1045    private List JavaDoc createAllDrawableLinesForItem(Graphics2D JavaDoc g2,
1046            LegendItem legendItem, double x, double y, double wordWrapWidth) {
1047        
1048        List JavaDoc drawableParts = new ArrayList JavaDoc();
1049
1050        DrawableLegendItem line
1051            = createDrawableLegendItem(g2, legendItem, x, y);
1052
1053        if (line.getWidth() < wordWrapWidth) {
1054            // we don't need word-wrapping, return just a single line.
1055
drawableParts.add(line);
1056            return drawableParts;
1057        }
1058        
1059        // we need word-wrapping. start laying out the lines. add words to
1060
// every line until it's full.
1061

1062        boolean firstLine = true;
1063        double totalHeight = y;
1064        String JavaDoc prefix = "";
1065        String JavaDoc suffix = legendItem.getLabel().trim();
1066        
1067        LegendItem tmpItem = new LegendItem(
1068            prefix.trim(),
1069            legendItem.getLabel(),
1070            legendItem.getToolTipText(),
1071            legendItem.getURLText(),
1072            legendItem.isShapeVisible(),
1073            legendItem.getShape(),
1074            legendItem.isShapeFilled(),
1075            legendItem.getFillPaint(),
1076            legendItem.isShapeOutlineVisible(),
1077            legendItem.getOutlinePaint(),
1078            legendItem.getOutlineStroke(),
1079            legendItem.isLineVisible(),
1080            legendItem.getLine(),
1081            legendItem.getLineStroke(),
1082            legendItem.getLinePaint()
1083        );
1084
1085        line = createDrawableLegendItem(g2, tmpItem, x, totalHeight);
1086        
1087        DrawableLegendItem goodLine = null; // no good known line yet.
1088

1089        do {
1090            // save the suffix, we might need to restore it.
1091
String JavaDoc prevSuffix = suffix;
1092
1093            // try to extend the prefix.
1094
int spacePos = suffix.indexOf(" ");
1095            if (spacePos < 0) {
1096                // no space found, append all the suffix to the prefix.
1097
prefix += suffix;
1098                suffix = "";
1099            }
1100            else {
1101                // move a word from suffix to prefix.
1102
prefix += suffix.substring(0, spacePos + 1);
1103                suffix = suffix.substring(spacePos + 1);
1104            }
1105            
1106            // Create a temporary legend item for the extended prefix.
1107
// If first line, make its marker visible.
1108
tmpItem = new LegendItem(
1109                prefix.trim(),
1110                legendItem.getLabel(),
1111                legendItem.getToolTipText(),
1112                legendItem.getURLText(),
1113                firstLine && legendItem.isShapeVisible(),
1114                legendItem.getShape(),
1115                firstLine && legendItem.isShapeFilled(),
1116                legendItem.getFillPaint(),
1117                firstLine && legendItem.isShapeOutlineVisible(),
1118                legendItem.getOutlinePaint(),
1119                legendItem.getOutlineStroke(),
1120                firstLine && legendItem.isLineVisible(),
1121                legendItem.getLine(),
1122                legendItem.getLineStroke(),
1123                legendItem.getLinePaint()
1124            );
1125            
1126            // and create a line for it as well.
1127
line = createDrawableLegendItem(g2, tmpItem, x, totalHeight);
1128
1129            // now check if line fits in width.
1130
if (line.getWidth() < wordWrapWidth) {
1131                // fits! save it as the last good known line.
1132
goodLine = line;
1133            }
1134            else {
1135                // doesn't fit. do we have a saved good line?
1136
if (goodLine == null) {
1137                    // nope. this means we will have to add it anyway and exceed
1138
// the desired wordWrapWidth. life is tough sometimes...
1139
drawableParts.add(line);
1140                    totalHeight += line.getHeight();
1141                }
1142                else {
1143                    // yep, we have a saved good line, and we intend to use it.
1144
drawableParts.add(goodLine);
1145                    totalHeight += goodLine.getHeight();
1146                    // restore previous suffix.
1147
suffix = prevSuffix;
1148                }
1149                // prepare to start a new line.
1150
firstLine = false;
1151                prefix = "";
1152                suffix = suffix.trim();
1153                line = null; // mark as used to avoid using twice.
1154
goodLine = null; // mark as used to avoid using twice.
1155
}
1156        }
1157        while (!suffix.equals(""));
1158        
1159        // make sure not to forget last line.
1160
if (line != null) {
1161            drawableParts.add(line);
1162        }
1163        
1164        return drawableParts;
1165    }
1166    
1167    /**
1168     * Creates a drawable legend item.
1169     * <P>
1170     * The marker box for each entry will be positioned next to the name of the
1171     * specified series within the legend area. The marker box will be square
1172     * and 70% of the height of current font.
1173     *
1174     * @param graphics the graphics context (supplies font metrics etc.).
1175     * @param legendItem the legend item.
1176     * @param x the upper left x coordinate for the bounding box.
1177     * @param y the upper left y coordinate for the bounding box.
1178     *
1179     * @return A legend item encapsulating all necessary info for drawing.
1180     */

1181    private DrawableLegendItem createDrawableLegendItem(Graphics2D JavaDoc graphics,
1182                                                        LegendItem legendItem,
1183                                                        double x, double y) {
1184
1185        int insideGap = 2;
1186        FontMetrics JavaDoc fm = graphics.getFontMetrics();
1187        LineMetrics JavaDoc lm = fm.getLineMetrics(legendItem.getLabel(), graphics);
1188        float textAscent = lm.getAscent();
1189        float lineHeight = textAscent + lm.getDescent() + lm.getLeading();
1190
1191        DrawableLegendItem item = new DrawableLegendItem(legendItem);
1192
1193        float xLabelLoc = (float) (x + insideGap + 1.15f * lineHeight);
1194        float yLabelLoc = (float) (y + insideGap + 0.5f * lineHeight);
1195
1196        item.setLabelPosition(new Point2D.Float JavaDoc(xLabelLoc, yLabelLoc));
1197
1198        float width = (float) (item.getLabelPosition().getX() - x
1199            + fm.stringWidth(legendItem.getLabel()) + 0.5 * textAscent);
1200
1201        float height = (2 * insideGap + lineHeight);
1202        item.setBounds(x, y, width, height);
1203        float boxDim = lineHeight * 0.70f;
1204        float xloc = (float) (x + insideGap + 0.15f * lineHeight);
1205        float yloc = (float) (y + insideGap + 0.15f * lineHeight);
1206        if (legendItem.isLineVisible()) {
1207            Line2D JavaDoc line = new Line2D.Float JavaDoc(
1208                xloc, yloc + boxDim / 2, xloc + boxDim * 3, yloc + boxDim / 2
1209            );
1210            item.setLine(line);
1211            // lengthen the bounds to accomodate the longer item
1212
item.setBounds(
1213                item.getX(), item.getY(), item.getWidth() + boxDim * 2,
1214                item.getHeight()
1215            );
1216            item.setLabelPosition(
1217                new Point2D.Float JavaDoc(xLabelLoc + boxDim * 2, yLabelLoc)
1218            );
1219            if (item.getItem().isShapeVisible()) {
1220                Shape JavaDoc marker = legendItem.getShape();
1221                AffineTransform JavaDoc t1 = AffineTransform.getScaleInstance(
1222                    this.shapeScaleX, this.shapeScaleY
1223                );
1224                Shape JavaDoc s1 = t1.createTransformedShape(marker);
1225                AffineTransform JavaDoc transformer
1226                    = AffineTransform.getTranslateInstance(
1227                        xloc + (boxDim * 1.5), yloc + boxDim / 2
1228                    );
1229                Shape JavaDoc s2 = transformer.createTransformedShape(s1);
1230                item.setMarker(s2);
1231           }
1232
1233        }
1234        else {
1235            if (item.getItem().isShapeVisible()) {
1236                Shape JavaDoc marker = legendItem.getShape();
1237                AffineTransform JavaDoc t1 = AffineTransform.getScaleInstance(
1238                        this.shapeScaleX, this.shapeScaleY
1239                );
1240                Shape JavaDoc s1 = t1.createTransformedShape(marker);
1241                AffineTransform JavaDoc transformer
1242                    = AffineTransform.getTranslateInstance(
1243                        xloc + boxDim / 2, yloc + boxDim / 2
1244                    );
1245                Shape JavaDoc s2 = transformer.createTransformedShape(s1);
1246                item.setMarker(s2);
1247            }
1248            else {
1249                item.setMarker(
1250                    new Rectangle2D.Float JavaDoc(xloc, yloc, boxDim, boxDim)
1251                );
1252            }
1253        }
1254        return item;
1255
1256    }
1257
1258    /**
1259     * Tests an object for equality with this legend.
1260     *
1261     * @param obj the object (<code>null</code> permitted).
1262     *
1263     * @return <code>true</code> or <code>false</code>.
1264     */

1265    public boolean equals(Object JavaDoc obj) {
1266
1267        if (obj == this) {
1268            return true;
1269        }
1270        if (!(obj instanceof DefaultOldLegend)) {
1271            return false;
1272        }
1273        DefaultOldLegend that = (DefaultOldLegend) obj;
1274        if (!super.equals(obj)) {
1275            return false;
1276        }
1277
1278        if (!ObjectUtilities.equal(this.margin, that.margin)) {
1279            return false;
1280        }
1281        if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
1282            return false;
1283        }
1284        if (!ObjectUtilities.equal(this.outlinePaint, that.outlinePaint)) {
1285            return false;
1286        }
1287        if (!ObjectUtilities.equal(
1288            this.backgroundPaint, that.backgroundPaint
1289        )) {
1290            return false;
1291        }
1292        if (!ObjectUtilities.equal(this.padding, that.padding)) {
1293            return false;
1294        }
1295        if (!ObjectUtilities.equal(this.title, that.title)) {
1296            return false;
1297        }
1298        if (!ObjectUtilities.equal(this.titleFont, that.titleFont)) {
1299            return false;
1300        }
1301        if (!ObjectUtilities.equal(this.itemFont, that.itemFont)) {
1302            return false;
1303        }
1304        if (!ObjectUtilities.equal(this.itemPaint, that.itemPaint)) {
1305            return false;
1306        }
1307        return true;
1308    }
1309
1310    /**
1311     * Provides serialization support.
1312     *
1313     * @param stream the output stream.
1314     *
1315     * @throws IOException if there is an I/O error.
1316     */

1317    private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc {
1318        stream.defaultWriteObject();
1319        SerialUtilities.writeStroke(this.outlineStroke, stream);
1320        SerialUtilities.writePaint(this.outlinePaint, stream);
1321        SerialUtilities.writePaint(this.backgroundPaint, stream);
1322        SerialUtilities.writePaint(this.itemPaint, stream);
1323    }
1324
1325    /**
1326     * Provides serialization support.
1327     *
1328     * @param stream the output stream.
1329     *
1330     * @throws IOException if there is an I/O error.
1331     * @throws ClassNotFoundException if there is a classpath problem.
1332     */

1333    private void readObject(ObjectInputStream JavaDoc stream)
1334        throws IOException JavaDoc, ClassNotFoundException JavaDoc {
1335        stream.defaultReadObject();
1336        this.outlineStroke = SerialUtilities.readStroke(stream);
1337        this.outlinePaint = SerialUtilities.readPaint(stream);
1338        this.backgroundPaint = SerialUtilities.readPaint(stream);
1339        this.itemPaint = SerialUtilities.readPaint(stream);
1340    }
1341
1342}
1343
Popular Tags