Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, 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-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: */ 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: public void distributeLabels(double minY, double height) { 73: sort(); // sorts the label records into ascending order by baseY 74: // if (isOverlap()) { 75: // adjustInwards(); 76: // } 77: // if still overlapping, do something else... 78: if (isOverlap()) { 79: adjustDownwards(minY, height); 80: } 81: 82: if (isOverlap()) { 83: adjustUpwards(minY, height); 84: } 85: 86: if (isOverlap()) { 87: spreadEvenly(minY, height); 88: } 89: } 90: 91: /** 92: * Returns <code>true</code> if there are overlapping labels in the list, 93: * and <code>false</code> otherwise. 94: * 95: * @return A boolean. 96: */ 97: private boolean isOverlap() { 98: double y = 0.0; 99: 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: }