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