001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2022, by David Gilbert. All rights reserved. 006 * 007 * https://github.com/jfree/orson-charts 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package org.jfree.chart3d.axis; 034 035import java.io.Serializable; 036import java.text.DecimalFormat; 037import java.text.Format; 038import org.jfree.chart3d.internal.Args; 039 040/** 041 * A {@link TickSelector} implementation that selects tick units in multiples 042 * of 1, 2 and 5. 043 * <br><br> 044 * NOTE: This class is serializable, but the serialization format is subject 045 * to change in future releases and should not be relied upon for persisting 046 * instances of this class. 047 */ 048@SuppressWarnings("serial") 049public class NumberTickSelector implements TickSelector, Serializable { 050 051 private int power = 0; 052 053 private int factor = 1; 054 055 /** 056 * A flag to track if the units are percentage values, in which case the 057 * formatter should display less decimal places. 058 */ 059 private boolean percentage; 060 061 /** 062 * Creates a new instance. 063 */ 064 public NumberTickSelector() { 065 this(false); 066 } 067 068 /** 069 * Creates a new instance, with the option to display the tick values as 070 * percentages. The axis follows the normal convention that values in the 071 * range 0.0 to 1.0 a represented as 0% to 100%. 072 * 073 * @param percentage format the tick values as percentages. 074 */ 075 public NumberTickSelector(boolean percentage) { 076 this.power = 0; 077 this.factor = 1; 078 this.percentage = percentage; 079 } 080 081 /** 082 * Selects and returns a standard tick size that is greater than or equal to 083 * the specified reference value and, ideally, as close to it as possible 084 * (to minimise the number of iterations used by axes to determine the tick 085 * size to use). After a call to this method, the 086 * {@link #getCurrentTickSize()} method should return the selected tick 087 * size (there is a "pointer" to this tick size), the {@link #next()} 088 * method should move the pointer to the next (larger) standard tick size, 089 * and the {@link #previous()} method should move the pointer to the 090 * previous (smaller) standard tick size. 091 * 092 * @param reference the reference value (must be positive and finite). 093 * 094 * @return The selected tick size. 095 */ 096 @Override 097 public double select(double reference) { 098 Args.finitePositiveRequired(reference, "reference"); 099 this.power = (int) Math.ceil(Math.log10(reference)); 100 this.factor = 1; 101 return getCurrentTickSize(); 102 } 103 104 /** 105 * Move the cursor to the next (larger) tick size, if there is one. 106 * Returns {@code true} in the case that the cursor is moved, and 107 * {@code false} where there are a finite number of tick sizes and the 108 * current tick size is the largest available. 109 */ 110 @Override 111 public boolean next() { 112 if (factor == 1) { 113 factor = 2; 114 return true; 115 } 116 if (factor == 2) { 117 factor = 5; 118 return true; 119 } 120 if (factor == 5) { 121 power++; 122 factor = 1; 123 return true; 124 } 125 throw new IllegalStateException("We should never get here."); 126 } 127 128 /** 129 * Move the cursor to the previous (smaller) tick size, if there is one. 130 * Returns {@code true} in the case that the cursor is moved, and 131 * {@code false} where there are a finite number of tick sizes and the 132 * current tick size is the smallest available. 133 */ 134 @Override 135 public boolean previous() { 136 if (factor == 1) { 137 factor = 5; 138 power--; 139 return true; 140 } 141 if (factor == 2) { 142 factor = 1; 143 return true; 144 } 145 if (factor == 5) { 146 factor = 2; 147 return true; 148 } 149 throw new IllegalStateException("We should never get here."); 150 } 151 152 @Override 153 public double getCurrentTickSize() { 154 return this.factor * Math.pow(10.0, this.power); 155 } 156 157 private final DecimalFormat dfNeg4 = new DecimalFormat("0.0000"); 158 private final DecimalFormat dfNeg3 = new DecimalFormat("0.000"); 159 private final DecimalFormat dfNeg2 = new DecimalFormat("0.00"); 160 private final DecimalFormat dfNeg1 = new DecimalFormat("0.0"); 161 private final DecimalFormat df0 = new DecimalFormat("#,##0"); 162 private final DecimalFormat dfNeg4P = new DecimalFormat("0.00%"); 163 private final DecimalFormat dfNeg3P = new DecimalFormat("0.0%"); 164 private final DecimalFormat dfNeg2P = new DecimalFormat("0%"); 165 private final DecimalFormat dfNeg1P = new DecimalFormat("0%"); 166 private final DecimalFormat df0P = new DecimalFormat("#,##0%"); 167 168 @Override 169 public Format getCurrentTickLabelFormat() { 170 if (power == -4) { 171 return this.percentage ? dfNeg4P : dfNeg4; 172 } 173 if (power == -3) { 174 return this.percentage ? dfNeg3P : dfNeg3; 175 } 176 if (power == -2) { 177 return this.percentage ? dfNeg2P : dfNeg2; 178 } 179 if (power == -1) { 180 return this.percentage ? dfNeg1P : dfNeg1; 181 } 182 if (power >= 0 && power <= 6) { 183 return this.percentage ? df0P : df0; 184 } 185 return this.percentage ? new DecimalFormat("0.0000E0%") 186 : new DecimalFormat("0.0000E0"); 187 } 188 189 /** 190 * Tests this instance for equality with an arbitrary object. 191 * 192 * @param obj the object ({@code null} permitted). 193 * 194 * @return A boolean. 195 */ 196 @Override 197 public boolean equals(Object obj) { 198 if (obj == this) { 199 return true; 200 } 201 if (!(obj instanceof NumberTickSelector)) { 202 return false; 203 } 204 NumberTickSelector that = (NumberTickSelector) obj; 205 if (this.percentage != that.percentage) { 206 return false; 207 } 208 return true; 209 } 210 211}