001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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-2007, 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 * 041 */ 042 043 package org.jfree.chart.plot; 044 045 import java.util.Collections; 046 047 /** 048 * This class distributes the section labels for one side of a pie chart so 049 * that they do not overlap. 050 */ 051 public class PieLabelDistributor extends AbstractPieLabelDistributor { 052 053 /** The minimum gap. */ 054 private double minGap = 4.0; 055 056 /** 057 * Creates a new distributor. 058 * 059 * @param labelCount the number of labels (ignored). 060 */ 061 public PieLabelDistributor(int labelCount) { 062 super(); 063 } 064 065 /** 066 * Distributes the labels. 067 * 068 * @param minY the minimum y-coordinate in Java2D-space. 069 * @param height the available height (in Java2D units). 070 */ 071 public void distributeLabels(double minY, double height) { 072 sort(); // sorts the label records into ascending order by baseY 073 if (isOverlap()) { 074 adjustInwards(); 075 } 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 /** 093 * Returns <code>true</code> if there are overlapping labels in the list, 094 * and <code>false</code> 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 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, record0.getLowerY() 170 - this.minGap - record1.getLabelHeight() / 2.0)); 171 } 172 } 173 } 174 175 /** 176 * Labels are spaced evenly in the available space in an attempt to 177 * eliminate the overlaps. 178 * 179 * @param minY the minimum y value (in Java2D coordinate space). 180 * @param height the height available for all labels. 181 */ 182 protected void spreadEvenly(double minY, double height) { 183 double y = minY; 184 double sumOfLabelHeights = 0.0; 185 for (int i = 0; i < this.labels.size(); i++) { 186 sumOfLabelHeights += getPieLabelRecord(i).getLabelHeight(); 187 } 188 double gap = Math.max(0, height - sumOfLabelHeights); 189 if (this.labels.size() > 1) { 190 gap = gap / (this.labels.size() - 1); 191 } 192 for (int i = 0; i < this.labels.size(); i++) { 193 PieLabelRecord record = getPieLabelRecord(i); 194 y = y + record.getLabelHeight() / 2.0; 195 record.setAllocatedY(y); 196 y = y + record.getLabelHeight() / 2.0 + gap; 197 } 198 } 199 200 /** 201 * Sorts the label records into ascending order by y-value. 202 */ 203 public void sort() { 204 Collections.sort(this.labels); 205 } 206 207 /** 208 * Returns a string containing a description of the object for 209 * debugging purposes. 210 * 211 * @return A string. 212 */ 213 public String toString() { 214 StringBuffer result = new StringBuffer(); 215 for (int i = 0; i < this.labels.size(); i++) { 216 result.append(getPieLabelRecord(i).toString()).append("\n"); 217 } 218 return result.toString(); 219 } 220 221 }