001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-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 044 package org.jfree.chart.plot; 045 046 import 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 */ 052 public 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 public void distributeLabels(double minY, double height) { 073 sort(); // sorts the label records into ascending order by baseY 074 // if (isOverlap()) { 075 // adjustInwards(); 076 // } 077 // if still overlapping, do something else... 078 if (isOverlap()) { 079 adjustDownwards(minY, height); 080 } 081 082 if (isOverlap()) { 083 adjustUpwards(minY, height); 084 } 085 086 if (isOverlap()) { 087 spreadEvenly(minY, height); 088 } 089 } 090 091 /** 092 * Returns <code>true</code> if there are overlapping labels in the list, 093 * and <code>false</code> otherwise. 094 * 095 * @return A boolean. 096 */ 097 private boolean isOverlap() { 098 double y = 0.0; 099 for (int i = 0; i < this.labels.size(); i++) { 100 PieLabelRecord plr = getPieLabelRecord(i); 101 if (y > plr.getLowerY()) { 102 return true; 103 } 104 y = plr.getUpperY(); 105 } 106 return false; 107 } 108 109 /** 110 * Adjusts the y-coordinate for the labels in towards the center in an 111 * attempt to fix overlapping. 112 */ 113 protected void adjustInwards() { 114 int lower = 0; 115 int upper = this.labels.size() - 1; 116 while (upper > lower) { 117 if (lower < upper - 1) { 118 PieLabelRecord r0 = getPieLabelRecord(lower); 119 PieLabelRecord r1 = getPieLabelRecord(lower + 1); 120 if (r1.getLowerY() < r0.getUpperY()) { 121 double adjust = r0.getUpperY() - r1.getLowerY() 122 + this.minGap; 123 r1.setAllocatedY(r1.getAllocatedY() + adjust); 124 } 125 } 126 PieLabelRecord r2 = getPieLabelRecord(upper - 1); 127 PieLabelRecord r3 = getPieLabelRecord(upper); 128 if (r2.getUpperY() > r3.getLowerY()) { 129 double adjust = (r2.getUpperY() - r3.getLowerY()) + this.minGap; 130 r3.setAllocatedY(r3.getAllocatedY() + adjust); 131 } 132 lower++; 133 upper--; 134 } 135 } 136 137 /** 138 * Any labels that are overlapping are moved down in an attempt to 139 * eliminate the overlaps. 140 * 141 * @param minY the minimum y value (in Java2D coordinate space). 142 * @param height the height available for all labels. 143 */ 144 protected void adjustDownwards(double minY, double height) { 145 for (int i = 0; i < this.labels.size() - 1; i++) { 146 PieLabelRecord record0 = getPieLabelRecord(i); 147 PieLabelRecord record1 = getPieLabelRecord(i + 1); 148 if (record1.getLowerY() < record0.getUpperY()) { 149 record1.setAllocatedY(Math.min(minY + height 150 - record1.getLabelHeight() / 2.0, 151 record0.getUpperY() + this.minGap 152 + record1.getLabelHeight() / 2.0)); 153 } 154 } 155 } 156 157 /** 158 * Any labels that are overlapping are moved up in an attempt to eliminate 159 * the overlaps. 160 * 161 * @param minY the minimum y value (in Java2D coordinate space). 162 * @param height the height available for all labels. 163 */ 164 protected void adjustUpwards(double minY, double height) { 165 for (int i = this.labels.size() - 1; i > 0; i--) { 166 PieLabelRecord record0 = getPieLabelRecord(i); 167 PieLabelRecord record1 = getPieLabelRecord(i - 1); 168 if (record1.getUpperY() > record0.getLowerY()) { 169 record1.setAllocatedY(Math.max(minY 170 + record1.getLabelHeight() / 2.0, record0.getLowerY() 171 - this.minGap - record1.getLabelHeight() / 2.0)); 172 } 173 } 174 } 175 176 /** 177 * Labels are spaced evenly in the available space in an attempt to 178 * eliminate the overlaps. 179 * 180 * @param minY the minimum y value (in Java2D coordinate space). 181 * @param height the height available for all labels. 182 */ 183 protected void spreadEvenly(double minY, double height) { 184 double y = minY; 185 double sumOfLabelHeights = 0.0; 186 for (int i = 0; i < this.labels.size(); i++) { 187 sumOfLabelHeights += getPieLabelRecord(i).getLabelHeight(); 188 } 189 double gap = height - sumOfLabelHeights; 190 if (this.labels.size() > 1) { 191 gap = gap / (this.labels.size() - 1); 192 } 193 for (int i = 0; i < this.labels.size(); i++) { 194 PieLabelRecord record = getPieLabelRecord(i); 195 y = y + record.getLabelHeight() / 2.0; 196 record.setAllocatedY(y); 197 y = y + record.getLabelHeight() / 2.0 + gap; 198 } 199 } 200 201 /** 202 * Sorts the label records into ascending order by y-value. 203 */ 204 public void sort() { 205 Collections.sort(this.labels); 206 } 207 208 /** 209 * Returns a string containing a description of the object for 210 * debugging purposes. 211 * 212 * @return A string. 213 */ 214 public String toString() { 215 StringBuffer result = new StringBuffer(); 216 for (int i = 0; i < this.labels.size(); i++) { 217 result.append(getPieLabelRecord(i).toString()).append("\n"); 218 } 219 return result.toString(); 220 } 221 222 }