001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * PieLabelDistributor.java 029 * ------------------------ 030 * (C) Copyright 2004, 2005, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: PieLabelDistributor.java,v 1.5.2.1 2005/10/25 20:52:08 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 08-Mar-2004 : Version 1 (DG); 040 * 18-Apr-2005 : Use StringBuffer (DG); 041 * 042 */ 043 044 package org.jfree.chart.plot; 045 046 import java.util.ArrayList; 047 import java.util.Collections; 048 import java.util.List; 049 050 /** 051 * This class distributes the section labels for one side of a pie chart so 052 * that they do not overlap. 053 */ 054 public class PieLabelDistributor { 055 056 /** The label records. */ 057 private List labels; 058 059 /** The minimum gap. */ 060 private double minGap = 4.0; 061 062 /** 063 * Creates a new distributor. 064 * 065 * @param labelCount the number of labels. 066 */ 067 public PieLabelDistributor(int labelCount) { 068 this.labels = new ArrayList(labelCount); 069 } 070 071 /** 072 * Returns a label record from the list. 073 * 074 * @param index the index. 075 * 076 * @return The label record. 077 */ 078 public PieLabelRecord getPieLabelRecord(int index) { 079 return (PieLabelRecord) this.labels.get(index); 080 } 081 082 /** 083 * Adds a label record. 084 * 085 * @param record the label record. 086 */ 087 public void addPieLabelRecord(PieLabelRecord record) { 088 this.labels.add(record); 089 } 090 091 /** 092 * Returns the number of items in the list. 093 * 094 * @return The item count. 095 */ 096 public int getItemCount() { 097 return this.labels.size(); 098 } 099 100 /** 101 * Distributes the labels. 102 * 103 * @param minY the minimum y-coordinate in Java2D-space. 104 * @param height the height. 105 */ 106 public void distributeLabels(double minY, double height) { 107 sort(); 108 if (isOverlap()) { 109 adjustInwards(); 110 } 111 112 // if still overlapping, do something else... 113 if (isOverlap()) { 114 adjustDownwards(minY, height); 115 } 116 117 if (isOverlap()) { 118 adjustUpwards(minY, height); 119 } 120 121 if (isOverlap()) { 122 spreadEvenly(minY, height); 123 } 124 125 } 126 127 /** 128 * Returns <code>true</code> if there are overlapping labels in the list, 129 * and <code>false</code> otherwise. 130 * 131 * @return A boolean. 132 */ 133 private boolean isOverlap() { 134 double y = 0.0; 135 for (int i = 0; i < this.labels.size(); i++) { 136 PieLabelRecord plr = getPieLabelRecord(i); 137 if (y > plr.getLowerY()) { 138 return true; 139 } 140 y = plr.getUpperY(); 141 } 142 return false; 143 } 144 145 /** 146 * Adjusts the y-coordinate for the labels in towards the center in an 147 * attempt to fix overlapping. 148 */ 149 protected void adjustInwards() { 150 int lower = 0; 151 int upper = this.labels.size() - 1; 152 while (upper > lower) { 153 if (lower < upper - 1) { 154 PieLabelRecord r0 = getPieLabelRecord(lower); 155 PieLabelRecord r1 = getPieLabelRecord(lower + 1); 156 if (r1.getLowerY() < r0.getUpperY()) { 157 double adjust = r0.getUpperY() - r1.getLowerY() 158 + this.minGap; 159 r1.setAllocatedY(r1.getAllocatedY() + adjust); 160 } 161 } 162 PieLabelRecord r2 = getPieLabelRecord(upper - 1); 163 PieLabelRecord r3 = getPieLabelRecord(upper); 164 if (r2.getUpperY() > r3.getLowerY()) { 165 double adjust = (r2.getUpperY() - r3.getLowerY()) + this.minGap; 166 r2.setAllocatedY(r2.getAllocatedY() - adjust); 167 } 168 lower++; 169 upper--; 170 } 171 } 172 173 /** 174 * Any labels that are overlapping are moved down in an attempt to 175 * eliminate the overlaps. 176 * 177 * @param minY the minimum y value (in Java2D coordinate space). 178 * @param height the height available for all labels. 179 */ 180 protected void adjustDownwards(double minY, double height) { 181 for (int i = 0; i < this.labels.size() - 1; i++) { 182 PieLabelRecord record0 = getPieLabelRecord(i); 183 PieLabelRecord record1 = getPieLabelRecord(i + 1); 184 if (record1.getLowerY() < record0.getUpperY()) { 185 record1.setAllocatedY( 186 Math.min( 187 minY + height, 188 record0.getUpperY() + this.minGap 189 + record1.getLabelHeight() / 2.0 190 ) 191 ); 192 } 193 } 194 } 195 196 /** 197 * Any labels that are overlapping are moved up in an attempt to eliminate 198 * the overlaps. 199 * 200 * @param minY the minimum y value (in Java2D coordinate space). 201 * @param height the height available for all labels. 202 */ 203 protected void adjustUpwards(double minY, double height) { 204 for (int i = this.labels.size() - 1; i > 0; i--) { 205 PieLabelRecord record0 = getPieLabelRecord(i); 206 PieLabelRecord record1 = getPieLabelRecord(i - 1); 207 if (record1.getUpperY() > record0.getLowerY()) { 208 record1.setAllocatedY( 209 Math.max( 210 minY, 211 record0.getLowerY() - this.minGap 212 - record1.getLabelHeight() / 2.0 213 ) 214 ); 215 } 216 } 217 } 218 219 /** 220 * Labels are spaced evenly in the available space in an attempt to 221 * eliminate the overlaps. 222 * 223 * @param minY the minimum y value (in Java2D coordinate space). 224 * @param height the height available for all labels. 225 */ 226 protected void spreadEvenly(double minY, double height) { 227 double y = minY; 228 double sumOfLabelHeights = 0.0; 229 for (int i = 0; i < this.labels.size(); i++) { 230 sumOfLabelHeights += getPieLabelRecord(i).getLabelHeight(); 231 } 232 double gap = height - sumOfLabelHeights; 233 if (this.labels.size() > 1) { 234 gap = gap / (this.labels.size() - 1); 235 } 236 for (int i = 0; i < this.labels.size(); i++) { 237 PieLabelRecord record = getPieLabelRecord(i); 238 y = y + record.getLabelHeight() / 2.0; 239 record.setAllocatedY(y); 240 y = y + record.getLabelHeight() / 2.0 + gap; 241 } 242 } 243 244 /** 245 * Sorts the label records into ascending order by y-value. 246 */ 247 public void sort() { 248 Collections.sort(this.labels); 249 } 250 251 /** 252 * Returns a string containing a description of the object for 253 * debugging purposes. 254 * 255 * @return A string. 256 */ 257 public String toString() { 258 StringBuffer result = new StringBuffer(); 259 for (int i = 0; i < this.labels.size(); i++) { 260 result.append(getPieLabelRecord(i).toString()).append("\n"); 261 } 262 return result.toString(); 263 } 264 265 }