001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 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 044package org.jfree.chart.plot; 045 046import 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 */ 052public 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 @Override 073 public void distributeLabels(double minY, double height) { 074 sort(); // sorts the label records into ascending order by baseY 075// if (isOverlap()) { 076// adjustInwards(); 077// } 078 // if still overlapping, do something else... 079 if (isOverlap()) { 080 adjustDownwards(minY, height); 081 } 082 083 if (isOverlap()) { 084 adjustUpwards(minY, height); 085 } 086 087 if (isOverlap()) { 088 spreadEvenly(minY, height); 089 } 090 } 091 092 /** 093 * Returns {@code true} if there are overlapping labels in the list, 094 * and {@code false} 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 - record1.getLabelHeight() / 2.0, 152 record0.getUpperY() + this.minGap 153 + record1.getLabelHeight() / 2.0)); 154 } 155 } 156 } 157 158 /** 159 * Any labels that are overlapping are moved up in an attempt to eliminate 160 * the overlaps. 161 * 162 * @param minY the minimum y value (in Java2D coordinate space). 163 * @param height the height available for all labels. 164 */ 165 protected void adjustUpwards(double minY, double height) { 166 for (int i = this.labels.size() - 1; i > 0; i--) { 167 PieLabelRecord record0 = getPieLabelRecord(i); 168 PieLabelRecord record1 = getPieLabelRecord(i - 1); 169 if (record1.getUpperY() > record0.getLowerY()) { 170 record1.setAllocatedY(Math.max(minY 171 + record1.getLabelHeight() / 2.0, record0.getLowerY() 172 - this.minGap - record1.getLabelHeight() / 2.0)); 173 } 174 } 175 } 176 177 /** 178 * Labels are spaced evenly in the available space in an attempt to 179 * eliminate the overlaps. 180 * 181 * @param minY the minimum y value (in Java2D coordinate space). 182 * @param height the height available for all labels. 183 */ 184 protected void spreadEvenly(double minY, double height) { 185 double y = minY; 186 double sumOfLabelHeights = 0.0; 187 for (int i = 0; i < this.labels.size(); i++) { 188 sumOfLabelHeights += getPieLabelRecord(i).getLabelHeight(); 189 } 190 double gap = height - sumOfLabelHeights; 191 if (this.labels.size() > 1) { 192 gap = gap / (this.labels.size() - 1); 193 } 194 for (int i = 0; i < this.labels.size(); i++) { 195 PieLabelRecord record = getPieLabelRecord(i); 196 y = y + record.getLabelHeight() / 2.0; 197 record.setAllocatedY(y); 198 y = y + record.getLabelHeight() / 2.0 + gap; 199 } 200 } 201 202 /** 203 * Sorts the label records into ascending order by y-value. 204 */ 205 public void sort() { 206 Collections.sort(this.labels); 207 } 208 209 /** 210 * Returns a string containing a description of the object for 211 * debugging purposes. 212 * 213 * @return A string. 214 */ 215 @Override 216 public String toString() { 217 StringBuilder result = new StringBuilder(); 218 for (int i = 0; i < this.labels.size(); i++) { 219 result.append(getPieLabelRecord(i).toString()).append("\n"); 220 } 221 return result.toString(); 222 } 223 224}