1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, 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
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * ------------------------
28  * PieLabelDistributor.java
29  * ------------------------
30  * (C) Copyright 2004-2008, by Object Refinery Limited and Contributors.
31  *
32  * Original Author:  David Gilbert (for Object Refinery Limited);
33  * Contributor(s):   -;
34  *
35  * Changes
36  * -------
37  * 08-Mar-2004 : Version 1 (DG);
38  * 18-Apr-2005 : Use StringBuffer (DG);
39  * 14-Jun-2007 : Now extends AbstractPieLabelDistributor (DG);
40  * 31-Mar-2008 : Fix bugs in label distribution (DG);
41  *
42  */
43 
44 package org.jfree.chart.plot;
45 
46 import java.util.Collections;
47 
48 /**
49  * This class distributes the section labels for one side of a pie chart so
50  * that they do not overlap.
51  */
52 public class PieLabelDistributor extends AbstractPieLabelDistributor {
53 
54     /** The minimum gap. */
55     private double minGap = 4.0;
56 
57     /**
58      * Creates a new distributor.
59      *
60      * @param labelCount  the number of labels (ignored).
61      */
PieLabelDistributor(int labelCount)62     public PieLabelDistributor(int labelCount) {
63         super();
64     }
65 
66     /**
67      * Distributes the labels.
68      *
69      * @param minY  the minimum y-coordinate in Java2D-space.
70      * @param height  the available height (in Java2D units).
71      */
72     @Override
distributeLabels(double minY, double height)73     public void distributeLabels(double minY, double height) {
74         sort();  // sorts the label records into ascending order by baseY
75 //        if (isOverlap()) {
76 //            adjustInwards();
77 //        }
78         // if still overlapping, do something else...
79         if (isOverlap()) {
80             adjustDownwards(minY, height);
81         }
82 
83         if (isOverlap()) {
84             adjustUpwards(minY, height);
85         }
86 
87         if (isOverlap()) {
88             spreadEvenly(minY, height);
89         }
90     }
91 
92     /**
93      * Returns <code>true</code> if there are overlapping labels in the list,
94      * and <code>false</code> otherwise.
95      *
96      * @return A boolean.
97      */
isOverlap()98     private boolean isOverlap() {
99         double y = 0.0;
100         for (int i = 0; i < this.labels.size(); i++) {
101             PieLabelRecord plr = getPieLabelRecord(i);
102             if (y > plr.getLowerY()) {
103                 return true;
104             }
105             y = plr.getUpperY();
106         }
107         return false;
108     }
109 
110     /**
111      * Adjusts the y-coordinate for the labels in towards the center in an
112      * attempt to fix overlapping.
113      */
adjustInwards()114     protected void adjustInwards() {
115         int lower = 0;
116         int upper = this.labels.size() - 1;
117         while (upper > lower) {
118             if (lower < upper - 1) {
119                 PieLabelRecord r0 = getPieLabelRecord(lower);
120                 PieLabelRecord r1 = getPieLabelRecord(lower + 1);
121                 if (r1.getLowerY() < r0.getUpperY()) {
122                     double adjust = r0.getUpperY() - r1.getLowerY()
123                                     + this.minGap;
124                     r1.setAllocatedY(r1.getAllocatedY() + adjust);
125                 }
126             }
127             PieLabelRecord r2 = getPieLabelRecord(upper - 1);
128             PieLabelRecord r3 = getPieLabelRecord(upper);
129             if (r2.getUpperY() > r3.getLowerY()) {
130                 double adjust = (r2.getUpperY() - r3.getLowerY()) + this.minGap;
131                 r3.setAllocatedY(r3.getAllocatedY() + adjust);
132             }
133             lower++;
134             upper--;
135         }
136     }
137 
138     /**
139      * Any labels that are overlapping are moved down in an attempt to
140      * eliminate the overlaps.
141      *
142      * @param minY  the minimum y value (in Java2D coordinate space).
143      * @param height  the height available for all labels.
144      */
adjustDownwards(double minY, double height)145     protected void adjustDownwards(double minY, double height) {
146         for (int i = 0; i < this.labels.size() - 1; i++) {
147             PieLabelRecord record0 = getPieLabelRecord(i);
148             PieLabelRecord record1 = getPieLabelRecord(i + 1);
149             if (record1.getLowerY() < record0.getUpperY()) {
150                 record1.setAllocatedY(Math.min(minY + height
151                         - record1.getLabelHeight() / 2.0,
152                         record0.getUpperY() + this.minGap
153                         + record1.getLabelHeight() / 2.0));
154             }
155         }
156     }
157 
158     /**
159      * Any labels that are overlapping are moved up in an attempt to eliminate
160      * the overlaps.
161      *
162      * @param minY  the minimum y value (in Java2D coordinate space).
163      * @param height  the height available for all labels.
164      */
adjustUpwards(double minY, double height)165     protected void adjustUpwards(double minY, double height) {
166         for (int i = this.labels.size() - 1; i > 0; i--) {
167             PieLabelRecord record0 = getPieLabelRecord(i);
168             PieLabelRecord record1 = getPieLabelRecord(i - 1);
169             if (record1.getUpperY() > record0.getLowerY()) {
170                 record1.setAllocatedY(Math.max(minY
171                         + record1.getLabelHeight() / 2.0, record0.getLowerY()
172                         - this.minGap - record1.getLabelHeight() / 2.0));
173             }
174         }
175     }
176 
177     /**
178      * Labels are spaced evenly in the available space in an attempt to
179      * eliminate the overlaps.
180      *
181      * @param minY  the minimum y value (in Java2D coordinate space).
182      * @param height  the height available for all labels.
183      */
spreadEvenly(double minY, double height)184     protected void spreadEvenly(double minY, double height) {
185         double y = minY;
186         double sumOfLabelHeights = 0.0;
187         for (int i = 0; i < this.labels.size(); i++) {
188             sumOfLabelHeights += getPieLabelRecord(i).getLabelHeight();
189         }
190         double gap = height - sumOfLabelHeights;
191         if (this.labels.size() > 1) {
192             gap = gap / (this.labels.size() - 1);
193         }
194         for (int i = 0; i < this.labels.size(); i++) {
195             PieLabelRecord record = getPieLabelRecord(i);
196             y = y + record.getLabelHeight() / 2.0;
197             record.setAllocatedY(y);
198             y = y + record.getLabelHeight() / 2.0 + gap;
199         }
200     }
201 
202     /**
203      * Sorts the label records into ascending order by y-value.
204      */
sort()205     public void sort() {
206         Collections.sort(this.labels);
207     }
208 
209     /**
210      * Returns a string containing a description of the object for
211      * debugging purposes.
212      *
213      * @return A string.
214      */
215     @Override
toString()216     public String toString() {
217         StringBuilder result = new StringBuilder();
218         for (int i = 0; i < this.labels.size(); i++) {
219             result.append(getPieLabelRecord(i).toString()).append("\n");
220         }
221         return result.toString();
222     }
223 
224 }
225