001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2000, Institut de Recherche pour le Développement
006: * (C) 1999, Pêches et Océans Canada
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.axis;
019:
020: // J2SE dependencies
021: import java.text.NumberFormat;
022: import java.util.Locale;
023:
024: // Geotools dependencies
025: import org.geotools.resources.XMath;
026:
027: /**
028: * Itérateur balayant les barres et étiquettes de graduation d'un axe.
029: * Cet itérateur retourne les positions des graduations à partir de la
030: * valeur minimale jusqu'à la valeur maximale.
031: *
032: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/axis/NumberIterator.java $
033: * @version $Id: NumberIterator.java 20883 2006-08-07 13:48:09Z jgarnett $
034: * @author Martin Desruisseaux
035: */
036: class NumberIterator implements TickIterator {
037: /**
038: * Petite quantité utilisée pour éviter les
039: * erreurs d'arrondissements dans les comparaisons.
040: */
041: private static final double EPS = 1E-8;
042:
043: /**
044: * Valeur de la première graduation principale.
045: * Cette valeur est fixée par {@link #init}.
046: */
047: private double minimum;
048:
049: /**
050: * Valeur limite des graduations. La dernière
051: * graduation n'aura pas nécessairement cette
052: * valeur. Cette valeur est fixée par {@link #init}.
053: */
054: private double maximum;
055:
056: /**
057: * Intervalle entre deux graduations principales.
058: * Cette valeur est fixée par {@link #init}.
059: */
060: private double increment;
061:
062: /**
063: * Longueur de l'axe (en points). Cette information
064: * est conservée afin d'éviter de refaire toute la
065: * procédure {@link #init} si les paramètres n'ont
066: * pas changés.
067: */
068: private float visualLength;
069:
070: /**
071: * Espace à laisser (en points) entre les graduations principales.
072: * Cette information est conservée afin d'éviter de refaire toute
073: * la procédure {@link #init} si les paramètres n'ont pas changés.
074: */
075: private float visualTickSpacing;
076:
077: /**
078: * Nombre de sous-divisions dans une graduation principale.
079: * Cette valeur est fixée par {@link #init}.
080: */
081: private int subTickCount;
082:
083: /**
084: * Index de la première sous-graduation
085: * dans la première graduation principale.
086: * Cette valeur est fixée par {@link #init}.
087: */
088: private int subTickStart;
089:
090: /**
091: * Index de la graduation principale en cours de traçage.
092: * Cette valeur commence à 0 et sera modifiée à chaque
093: * appel à {@link #next}.
094: */
095: private int tickIndex;
096:
097: /**
098: * Index de la graduation secondaire en cours de traçage. Cette
099: * valeur va de 0 inclusivement jusqu'à {@link #subTickCount}
100: * exclusivement. Elle sera modifiée à chaque appel à {@link #next}.
101: */
102: private int subTickIndex;
103:
104: /**
105: * Valeur de la graduation principale ou secondaire actuelle.
106: * Cette valeur sera modifiée à chaque appel à {@link #next}.
107: */
108: private double value;
109:
110: /**
111: * Format à utiliser pour écrire les étiquettes de graduation. Ce format ne
112: * sera construit que la première fois où {@link #currentLabel} sera appelée.
113: */
114: private transient NumberFormat format;
115:
116: /**
117: * Indique si {@link #format} est valide. Le format peut
118: * devenir invalide si {@link #init} a été appelée. Dans
119: * ce cas, il peut falloir changer le nombre de chiffres
120: * après la virgule qu'il écrit.
121: */
122: private transient boolean formatValid;
123:
124: /**
125: * Conventions à utiliser pour
126: * le formatage des nombres.
127: */
128: private Locale locale;
129:
130: /**
131: * Construit un itérateur par défaut. La méthode {@link #init}
132: * <u>doit</u> être appelée avant que cet itérateur ne soit
133: * utilisable.
134: *
135: * @param locale Conventions à utiliser pour le formatage des nombres.
136: */
137: protected NumberIterator(final Locale locale) {
138: this .locale = locale;
139: }
140:
141: /**
142: * Initialise l'itérateur.
143: *
144: * @param minimum Valeur minimale de la première graduation.
145: * @param maximum Valeur limite des graduations. La dernière
146: * graduation n'aura pas nécessairement cette valeur.
147: * @param visualLength Longueur visuelle de l'axe sur laquelle tracer la graduation.
148: * Cette longueur doit être exprimée en pixels ou en points.
149: * @param visualTickSpacing Espace à laisser visuellement entre deux marques de graduation.
150: * Cet espace doit être exprimé en pixels ou en points (1/72 de pouce).
151: */
152: protected void init(double minimum, final double maximum,
153: final float visualLength, final float visualTickSpacing) {
154: if (minimum == this .minimum && maximum == this .maximum
155: && visualLength == this .visualLength
156: && visualTickSpacing == this .visualTickSpacing) {
157: rewind();
158: return;
159: }
160: AbstractGraduation.ensureFinite("minimum", minimum);
161: AbstractGraduation.ensureFinite("maximum", maximum);
162: AbstractGraduation.ensureFinite("visualLength", visualLength); // May be 0.
163: AbstractGraduation.ensureNonNull("visualTickSpacing",
164: visualTickSpacing);
165: this .visualLength = visualLength;
166: this .visualTickSpacing = visualTickSpacing;
167: /*
168: * Estime le pas qui donnera au moins l'espacement spécifié entre
169: * chaque graduation. Détermine ensuite si ce pas est de l'ordre
170: * des dizaines, centaines ou autre et on ramènera temporairement
171: * ce pas à l'ordre des unitées.
172: */
173: double increment = (maximum - minimum)
174: * (visualTickSpacing / visualLength);
175: final double factor = XMath.pow10((int) Math.floor(XMath
176: .log10(increment)));
177: increment /= factor;
178: if (Double.isNaN(increment) || Double.isInfinite(increment)
179: || increment == 0) {
180: this .minimum = minimum;
181: this .maximum = maximum;
182: this .increment = Double.NaN;
183: this .value = Double.NaN;
184: this .tickIndex = 0;
185: this .subTickIndex = 0;
186: this .subTickStart = 0;
187: this .subTickCount = 1;
188: this .formatValid = false;
189: return;
190: }
191: /*
192: * Le pas se trouve maintenant entre 1 et 10. On l'ajuste maintenant
193: * pour lui donner des valeurs qui ne sont habituellement pas trop
194: * difficiles à lire.
195: */
196: final int subTickCount;
197: if (increment <= 1.0) {
198: increment = 1.0;
199: subTickCount = 5;
200: } else if (increment <= 2.0) {
201: increment = 2.0;
202: subTickCount = 4;
203: } else if (increment <= 2.5) {
204: increment = 2.5;
205: subTickCount = 5;
206: } else if (increment <= 4.0) {
207: increment = 4.0;
208: subTickCount = 4;
209: } else if (increment <= 5.0) {
210: increment = 5.0;
211: subTickCount = 5;
212: } else {
213: increment = 10.0;
214: subTickCount = 5;
215: }
216: increment = increment * factor;
217: /*
218: * Arrondie maintenant le minimum sur une des graduations principales.
219: * Détermine ensuite combien de graduations secondaires il faut sauter
220: * sur la première graduation principale.
221: */
222: final double tmp = minimum;
223: minimum = Math.floor(minimum / increment + EPS) * increment;
224: int subTickStart = (int) Math.ceil((tmp - minimum - EPS)
225: * (subTickCount / increment));
226: final int extra = subTickStart / subTickCount;
227: minimum += extra * increment;
228: subTickStart -= extra * subTickCount;
229:
230: this .increment = increment;
231: this .subTickCount = subTickCount;
232: this .maximum = maximum + Math.abs(maximum * EPS);
233: this .minimum = minimum;
234: this .subTickStart = subTickStart;
235: this .subTickIndex = subTickStart;
236: this .tickIndex = 0;
237: this .value = minimum + increment
238: * (subTickStart / (double) subTickCount);
239: this .formatValid = false;
240: }
241:
242: /**
243: * Indique s'il reste des graduations à retourner. Cette méthode retourne {@code true}
244: * tant que {@link #currentValue} ou {@link #currentLabel} peuvent être appelées.
245: */
246: public boolean hasNext() {
247: return value <= maximum;
248: }
249:
250: /**
251: * Indique si la graduation courante est une graduation majeure.
252: *
253: * @return {@code true} si la graduation courante est une
254: * graduation majeure, ou {@code false} si elle
255: * est une graduation mineure.
256: */
257: public boolean isMajorTick() {
258: return subTickIndex == 0;
259: }
260:
261: /**
262: * Returns the position where to draw the current tick. The position is scaled
263: * from the graduation's minimum to maximum. This is usually the same number
264: * than {@link #currentValue}. The mean exception is for logarithmic graduation,
265: * in which the tick position is not proportional to the tick value.
266: */
267: public double currentPosition() {
268: return value;
269: }
270:
271: /**
272: * Retourne la valeur de la graduation courante. Cette méthode
273: * peut être appelée pour une graduation majeure ou mineure.
274: */
275: public double currentValue() {
276: return value;
277: }
278:
279: /**
280: * Retourne l'étiquette de la graduation courante. On n'appele généralement
281: * cette méthode que pour les graduations majeures, mais elle peut aussi
282: * être appelée pour les graduations mineures. Cette méthode retourne
283: * {@code null} s'il n'y a pas d'étiquette pour la graduation courante.
284: */
285: public String currentLabel() {
286: if (!formatValid) {
287: if (format == null) {
288: format = NumberFormat.getNumberInstance(locale);
289: }
290: /*
291: * Trouve le nombre de chiffres après la virgule nécessaires pour représenter les
292: * étiquettes de la graduation. Impose une limite de six chiffres, limite qui pourrait
293: * être atteinte notamment avec les nombres périodiques (par exemple des intervalles
294: * de temps exprimés en fractions de jours).
295: */
296: int precision;
297: double step = Math.abs(increment);
298: for (precision = 0; precision < 6; precision++) {
299: final double check = Math.rint(step * 1E+4) % 1E+4;
300: if (!(check > step * EPS)) { // 'step' may be NaN
301: break;
302: }
303: step *= 10;
304: }
305: format.setMinimumFractionDigits(precision);
306: format.setMaximumFractionDigits(precision);
307: formatValid = true;
308: }
309: return format.format(currentValue());
310: }
311:
312: /**
313: * Passe à la graduation suivante.
314: */
315: public void next() {
316: if (++subTickIndex >= subTickCount) {
317: subTickIndex = 0;
318: tickIndex++;
319: }
320: // On n'utilise pas "+=" afin d'éviter les erreurs d'arrondissements.
321: value = minimum + increment
322: * (tickIndex + subTickIndex / (double) subTickCount);
323: }
324:
325: /**
326: * Passe directement à la graduation majeure suivante.
327: */
328: public void nextMajor() {
329: subTickIndex = 0;
330: value = minimum + increment * (++tickIndex);
331: }
332:
333: /**
334: * Replace l'itérateur sur la première graduation.
335: */
336: public void rewind() {
337: tickIndex = 0;
338: subTickIndex = subTickStart;
339: value = minimum + increment
340: * (subTickStart / (double) subTickCount);
341: }
342:
343: /**
344: * Retourne les conventions à utiliser pour
345: * écrire les étiquettes de graduation.
346: */
347: public final Locale getLocale() {
348: return locale;
349: }
350:
351: /**
352: * Modifie les conventions à utiliser pour
353: * écrire les étiquettes de graduation.
354: */
355: public final void setLocale(final Locale locale) {
356: if (!locale.equals(this .locale)) {
357: this .locale = locale;
358: this .format = null;
359: formatValid = false;
360: }
361: }
362: }
|