001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2020, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ------------------------
028 * PieLabelDistributor.java
029 * ------------------------
030 * (C) Copyright 2004-2008, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 08-Mar-2004 : Version 1 (DG);
038 * 18-Apr-2005 : Use StringBuffer (DG);
039 * 14-Jun-2007 : Now extends AbstractPieLabelDistributor (DG);
040 * 31-Mar-2008 : Fix bugs in label distribution (DG);
041 *
042 */
043
044package org.jfree.chart.plot;
045
046import java.util.Collections;
047
048/**
049 * This class distributes the section labels for one side of a pie chart so
050 * that they do not overlap.
051 */
052public class PieLabelDistributor extends AbstractPieLabelDistributor {
053
054    /** The minimum gap. */
055    private double minGap = 4.0;
056
057    /**
058     * Creates a new distributor.
059     *
060     * @param labelCount  the number of labels (ignored).
061     */
062    public PieLabelDistributor(int labelCount) {
063        super();
064    }
065
066    /**
067     * Distributes the labels.
068     *
069     * @param minY  the minimum y-coordinate in Java2D-space.
070     * @param height  the available height (in Java2D units).
071     */
072    @Override
073    public void distributeLabels(double minY, double height) {
074        sort();  // sorts the label records into ascending order by baseY
075//        if (isOverlap()) {
076//            adjustInwards();
077//        }
078        // if still overlapping, do something else...
079        if (isOverlap()) {
080            adjustDownwards(minY, height);
081        }
082
083        if (isOverlap()) {
084            adjustUpwards(minY, height);
085        }
086
087        if (isOverlap()) {
088            spreadEvenly(minY, height);
089        }
090    }
091
092    /**
093     * Returns {@code true} if there are overlapping labels in the list,
094     * and {@code false} otherwise.
095     *
096     * @return A boolean.
097     */
098    private boolean isOverlap() {
099        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     */
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     */
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     */
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     */
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     */
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
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}