/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 1999-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.display.axis; import java.text.NumberFormat; import org.apache.sis.math.MathFunctions; import org.geotoolkit.internal.InternalUtilities; /** * Itérateur balayant les barres et étiquettes de graduation d'un axe. * Cet itérateur retourne les positions des graduations à partir de la * valeur minimale jusqu'à la valeur maximale. * * @author Martin Desruisseaux (MPO, IRD) * @version 3.00 * * @since 2.0 * @module */ class NumberIterator implements TickIterator { /** * Petite quantité utilisée pour éviter les * erreurs d'arrondissements dans les comparisons. */ private static final double EPS = 1E-8; /** * Valeur de la première graduation principale. * Cette valeur est fixée par {@link #init}. */ private double minimum; /** * Valeur limite des graduations. La dernière graduation n'aura pas * nécessairement cette valeur. Cette valeur est fixée par {@link #init}. */ private double maximum; /** * Intervalle entre deux graduations principales. * Cette valeur est fixée par {@link #init}. */ private double increment; /** * Longueur de l'axe (en points). Cette information est conservée afin d'éviter de * refaire toute la procédure {@link #init} si les paramètres n'ont pas changés. */ private float visualLength; /** * Espace à laisser (en points) entre les graduations principales. * Cette information est conservée afin d'éviter de refaire toute * la procédure {@link #init} si les paramètres n'ont pas changés. */ private float visualTickSpacing; /** * Nombre de sous-divisions dans une graduation principale. * Cette valeur est fixée par {@link #init}. */ private int subTickCount; /** * Index de la première sous-graduation dans la première graduation principale. * Cette valeur est fixée par {@link #init}. */ private int subTickStart; /** * Index de la graduation principale en cours de traçage. Cette valeur * commence à 0 et sera modifiée à chaque appel à {@link #next}. */ private int tickIndex; /** * Index de la graduation secondaire en cours de traçage. Cette * valeur va de 0 inclusivement jusqu'à {@link #subTickCount} * exclusivement. Elle sera modifiée à chaque appel à {@link #next}. */ private int subTickIndex; /** * Valeur de la graduation principale ou secondaire actuelle. * Cette valeur sera modifiée à chaque appel à {@link #next}. */ private double value; /** * Format à utiliser pour écrire les étiquettes de graduation. Ce format ne * sera construit que la première fois où {@link #currentLabel} sera appelée. */ private transient NumberFormat format; /** * The original format. Used in order to determine if {@link #format} needs to be cloned. */ private NumberFormat originalFormat; /** * Indique si {@link #format} est valide. Le format peut devenir invalide si * {@link #init} a été appelée. Dans ce cas, il peut falloir changer le nombre * de chiffres après la virgule qu'il écrit. */ private transient boolean formatValid; /** * Construit un itérateur par défaut. La méthode {@link #init} * <u>doit</u> être appelée avant que cet itérateur ne soit utilisable. */ protected NumberIterator(final NumberFormat format) { this.format = originalFormat = format; } /** * Initialise l'itérateur. * * @param minimum * Valeur minimale de la première graduation. * @param maximum * Valeur limite des graduations. La dernière graduation n'aura pas * nécessairement cette valeur. * @param visualLength * Longueur visuelle de l'axe sur laquelle tracer la graduation. * Cette longueur doit être exprimée en pixels ou en points. * @param visualTickSpacing * Espace à laisser visuellement entre deux marques de graduation. * Cet espace doit être exprimé en pixels ou en points (1/72 de pouce). */ protected void init(double minimum, final double maximum, final float visualLength, final float visualTickSpacing) { if (minimum == this.minimum && maximum == this.maximum && visualLength == this.visualLength && visualTickSpacing == this.visualTickSpacing) { rewind(); return; } AbstractGraduation.ensureFinite ("minimum", minimum); AbstractGraduation.ensureFinite ("maximum", maximum); AbstractGraduation.ensureFinite ("visualLength", visualLength); // May be 0. AbstractGraduation.ensureNonZero("visualTickSpacing", visualTickSpacing); this.visualLength = visualLength; this.visualTickSpacing = visualTickSpacing; /* * Estime le pas qui donnera au moins l'espacement spécifié entre * chaque graduation. Détermine ensuite si ce pas est de l'ordre * des dizaines, centaines ou autre et on ramènera temporairement * ce pas à l'ordre des unitées. */ double increment = (maximum - minimum) * (visualTickSpacing / visualLength); final double factor = MathFunctions.pow10((int) Math.floor(Math.log10(increment))); increment /= factor; if (Double.isNaN(increment) || Double.isInfinite(increment) || increment==0) { this.minimum = minimum; this.maximum = maximum; this.increment = Double.NaN; this.value = Double.NaN; this.tickIndex = 0; this.subTickIndex = 0; this.subTickStart = 0; this.subTickCount = 1; this.formatValid = false; return; } /* * Le pas se trouve maintenant entre 1 et 10. On l'ajuste maintenant * pour lui donner des valeurs qui ne sont habituellement pas trop * difficiles à lire. */ final int subTickCount; if (increment <= 1.0) {increment = 1.0; subTickCount=5;} else if (increment <= 2.0) {increment = 2.0; subTickCount=4;} else if (increment <= 2.5) {increment = 2.5; subTickCount=5;} else if (increment <= 4.0) {increment = 4.0; subTickCount=4;} else if (increment <= 5.0) {increment = 5.0; subTickCount=5;} else {increment =10.0; subTickCount=5;} increment = increment*factor; /* * Arrondie maintenant le minimum sur une des graduations principales. * Détermine ensuite combien de graduations secondaires il faut sauter * sur la première graduation principale. */ final double tmp = minimum; minimum = Math.floor(minimum/increment+EPS)*increment; int subTickStart = (int)Math.ceil((tmp-minimum-EPS)*(subTickCount/increment)); final int extra = subTickStart / subTickCount; minimum += extra*increment; subTickStart -= extra*subTickCount; this.increment = increment; this.subTickCount = subTickCount; this.maximum = maximum + Math.abs(maximum*EPS); this.minimum = minimum; this.subTickStart = subTickStart; this.subTickIndex = subTickStart; this.tickIndex = 0; this.value = minimum + increment*(subTickStart/(double)subTickCount); this.formatValid = false; } /** * {@inheritDoc} */ @Override public boolean isDone() { return !(value <= maximum); // Use '!' for catching NaN. } /** * {@inheritDoc} */ @Override public boolean isMajorTick() { return subTickIndex == 0; } /** * {@inheritDoc} */ @Override public double currentPosition() { return value; } /** * {@inheritDoc} */ @Override public double currentValue() { return value; } /** * {@inheritDoc} */ @Override public String currentLabel() { if (!formatValid) { if (format == originalFormat) { format = (NumberFormat) format.clone(); } /* * Trouve le nombre de chiffres après la virgule nécessaires pour représenter les * étiquettes de la graduation. Impose une limite de six chiffres, limite qui pourrait * être atteinte notamment avec les nombres périodiques (par exemple des intervalles * de temps exprimés en fractions de jours). */ InternalUtilities.configure(format, increment, 6); formatValid = true; } return format.format(currentValue()); } /** * {@inheritDoc} */ @Override public void next() { if (++subTickIndex >= subTickCount) { subTickIndex = 0; tickIndex++; } // On n'utilise pas "+=" afin d'éviter les erreurs d'arrondissements. value = minimum + increment * (tickIndex + subTickIndex / (double)subTickCount); } /** * {@inheritDoc} */ @Override public void nextMajor() { subTickIndex = 0; value = minimum + increment * (++tickIndex); } /** * {@inheritDoc} */ @Override public void rewind() { tickIndex = 0; subTickIndex = subTickStart; value = minimum + increment*(subTickStart/(double)subTickCount); } /** * Modifie les conventions à utiliser pour * écrire les étiquettes de graduation. */ public final void setFormat(final NumberFormat format) { if (!format.equals(originalFormat)) { this.format = originalFormat = format; formatValid = false; } } }