Source for org.jfree.chart.plot.PieLabelDistributor

   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
  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:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ------------------------
  28:  * PieLabelDistributor.java
  29:  * ------------------------
  30:  * (C) Copyright 2004, 2005, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: PieLabelDistributor.java,v 1.5.2.1 2005/10/25 20:52:08 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 08-Mar-2004 : Version 1 (DG);
  40:  * 18-Apr-2005 : Use StringBuffer (DG);
  41:  *
  42:  */
  43: 
  44: package org.jfree.chart.plot;
  45: 
  46: import java.util.ArrayList;
  47: import java.util.Collections;
  48: import java.util.List;
  49: 
  50: /**
  51:  * This class distributes the section labels for one side of a pie chart so 
  52:  * that they do not overlap.
  53:  */
  54: public class PieLabelDistributor {
  55:     
  56:     /** The label records. */
  57:     private List labels;
  58:     
  59:     /** The minimum gap. */
  60:     private double minGap = 4.0;
  61:     
  62:     /**
  63:      * Creates a new distributor.
  64:      * 
  65:      * @param labelCount  the number of labels.
  66:      */
  67:     public PieLabelDistributor(int labelCount) {
  68:         this.labels = new ArrayList(labelCount);
  69:     }
  70:     
  71:     /**
  72:      * Returns a label record from the list.
  73:      * 
  74:      * @param index  the index.
  75:      * 
  76:      * @return The label record.
  77:      */
  78:     public PieLabelRecord getPieLabelRecord(int index) {
  79:         return (PieLabelRecord) this.labels.get(index);   
  80:     }
  81:     
  82:     /**
  83:      * Adds a label record.
  84:      * 
  85:      * @param record  the label record.
  86:      */
  87:     public void addPieLabelRecord(PieLabelRecord record) {
  88:         this.labels.add(record);
  89:     }
  90:     
  91:     /**
  92:      * Returns the number of items in the list.
  93:      * 
  94:      * @return The item count.
  95:      */
  96:     public int getItemCount() {
  97:         return this.labels.size();   
  98:     }
  99:     
 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: }