001/* ====================================================== 002 * JFreeChart : a chart library for the Java(tm) platform 003 * ====================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: https://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 * NumberTickUnitSource.java 029 * ------------------------- 030 * (C) Copyright 2014-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.chart.axis; 038 039import java.io.Serializable; 040import java.text.DecimalFormat; 041import java.text.NumberFormat; 042import java.util.Objects; 043 044/** 045 * A tick unit source implementation that returns NumberTickUnit instances 046 * that are multiples of 1, 2 or 5 times some power of 10. 047 */ 048public class NumberTickUnitSource implements TickUnitSource, Serializable { 049 050 /** Show integers only? */ 051 private final boolean integers; 052 053 /** The power exponent. */ 054 private int power; 055 056 /** The factor (1, 2 or 5) */ 057 private int factor; 058 059 /** The number formatter to use (an override, it can be null). */ 060 private final NumberFormat formatter; 061 062 /** 063 * Creates a new instance. 064 */ 065 public NumberTickUnitSource() { 066 this(false); 067 } 068 069 /** 070 * Creates a new instance. 071 * 072 * @param integers show integers only. 073 */ 074 public NumberTickUnitSource(boolean integers) { 075 this(integers, null); 076 } 077 078 /** 079 * Creates a new instance. 080 * 081 * @param integers show integers only? 082 * @param formatter a formatter for the axis tick labels ({@code null} 083 * permitted). 084 */ 085 public NumberTickUnitSource(boolean integers, NumberFormat formatter) { 086 this.integers = integers; 087 this.formatter = formatter; 088 this.power = 0; 089 this.factor = 1; 090 } 091 092 @Override 093 public TickUnit getLargerTickUnit(TickUnit unit) { 094 TickUnit t = getCeilingTickUnit(unit); 095 if (t.equals(unit)) { 096 next(); 097 t = new NumberTickUnit(getTickSize(), getTickLabelFormat(), 098 getMinorTickCount()); 099 } 100 return t; 101 } 102 103 @Override 104 public TickUnit getCeilingTickUnit(TickUnit unit) { 105 return getCeilingTickUnit(unit.getSize()); 106 } 107 108 @Override 109 public TickUnit getCeilingTickUnit(double size) { 110 if (Double.isInfinite(size)) { 111 throw new IllegalArgumentException("Must be finite."); 112 } 113 this.power = (int) Math.ceil(Math.log10(size)); 114 if (this.integers) { 115 power = Math.max(this.power, 0); 116 } 117 this.factor = 1; 118 boolean done = false; 119 // step down in size until the current size is too small or there are 120 // no more units 121 while (!done) { 122 done = !previous(); 123 if (getTickSize() < size) { 124 next(); 125 done = true; 126 } 127 } 128 return new NumberTickUnit(getTickSize(), getTickLabelFormat(), 129 getMinorTickCount()); 130 } 131 132 private boolean next() { 133 if (factor == 1) { 134 factor = 2; 135 return true; 136 } 137 if (factor == 2) { 138 factor = 5; 139 return true; 140 } 141 if (factor == 5) { 142 if (power == 300) { 143 return false; 144 } 145 power++; 146 factor = 1; 147 return true; 148 } 149 throw new IllegalStateException("We should never get here."); 150 } 151 152 private boolean previous() { 153 if (factor == 1) { 154 if (this.integers && power == 0 || power == -300) { 155 return false; 156 } 157 factor = 5; 158 power--; 159 return true; 160 } 161 if (factor == 2) { 162 factor = 1; 163 return true; 164 } 165 if (factor == 5) { 166 factor = 2; 167 return true; 168 } 169 throw new IllegalStateException("We should never get here."); 170 } 171 172 private double getTickSize() { 173 return this.factor * Math.pow(10.0, this.power); 174 } 175 176 /** Formatter with 4 decimal places. */ 177 private final DecimalFormat dfNeg4 = new DecimalFormat("0.0000"); 178 /** Formatter with 3 decimal places. */ 179 private final DecimalFormat dfNeg3 = new DecimalFormat("0.000"); 180 /** Formatter with 2 decimal places. */ 181 private final DecimalFormat dfNeg2 = new DecimalFormat("0.00"); 182 /** Formatter with 1 decimal place. */ 183 private final DecimalFormat dfNeg1 = new DecimalFormat("0.0"); 184 /** Formatter for integers. */ 185 private final DecimalFormat df0 = new DecimalFormat("#,##0"); 186 /** Formatter for general numbers. */ 187 private final DecimalFormat df = new DecimalFormat("#.######E0"); 188 189 private NumberFormat getTickLabelFormat() { 190 if (this.formatter != null) { 191 return this.formatter; 192 } 193 if (power == -4) { 194 return dfNeg4; 195 } 196 if (power == -3) { 197 return dfNeg3; 198 } 199 if (power == -2) { 200 return dfNeg2; 201 } 202 if (power == -1) { 203 return dfNeg1; 204 } 205 if (power >= 0 && power <= 6) { 206 return df0; 207 } 208 return df; 209 } 210 211 private int getMinorTickCount() { 212 if (factor == 1) { 213 return 10; 214 } else if (factor == 5) { 215 return 5; 216 } 217 return 0; 218 } 219 220 @Override 221 public boolean equals(Object obj) { 222 if (obj == this) { 223 return true; 224 } 225 if (!(obj instanceof NumberTickUnitSource)) { 226 return false; 227 } 228 NumberTickUnitSource that = (NumberTickUnitSource) obj; 229 if (this.integers != that.integers) { 230 return false; 231 } 232 if (!Objects.equals(this.formatter, that.formatter)) { 233 return false; 234 } 235 if (this.power != that.power) { 236 return false; 237 } 238 if (this.factor != that.factor) { 239 return false; 240 } 241 return true; 242 } 243}