/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 1999-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.axis; import java.awt.RenderingHints; import java.text.Format; import java.text.NumberFormat; import java.util.Locale; import static java.lang.Double.doubleToLongBits; import javax.measure.unit.Unit; import javax.measure.converter.UnitConverter; import javax.measure.converter.ConversionException; /** * A graduation using numbers on a linear axis. * * @since 2.0 * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (PMO, IRD) */ public class NumberGraduation extends AbstractGraduation { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -3074504745332240845L; /** * The minimal value for this graduation. Default to 0. */ private double minimum = 0; /** * The maximal value for this graduation. Default to 10. */ private double maximum = 10; /** * Constructs a graduation with the supplied units. * * @param unit The axis's units, or {@code null} if unknow. */ public NumberGraduation(final Unit<?> unit) { super(unit); } /** * Sets the minimum value for this graduation. If the new minimum is greater * than the current maximum, then the maximum will also be set to a value * greater than or equals to the minimum. * * @param value The new minimum in {@link #getUnit} units. * @return {@code true} if the state of this graduation changed * as a result of this call, or {@code false} if the new * value is identical to the previous one. * @throws IllegalArgumentException Si {@code value} is NaN ou infinite. * * @see #getMinimum * @see #setMaximum(double) */ public synchronized boolean setMinimum(final double value) throws IllegalArgumentException { ensureFinite("minimum", value); double old = minimum; minimum = value; final Double valueObject = value; listenerList.firePropertyChange("minimum", old, valueObject); if (maximum < value) { old = maximum; maximum = value; listenerList.firePropertyChange("maximum", old, valueObject); return true; } return doubleToLongBits(value) != doubleToLongBits(old); } /** * Set the maximum value for this graduation. If the new maximum is less than the current * minimum, then the minimum will also be set to a value less than or equals to the maximum. * * @param value The new maximum in {@link #getUnit} units. * @return {@code true} if the state of this graduation changed as a result of this call, * or {@code false} if the new value is identical to the previous one. * @throws IllegalArgumentException If {@code value} is NaN ou infinite. * * @see #getMaximum * @see #setMinimum(double) */ public synchronized boolean setMaximum(final double value) throws IllegalArgumentException { ensureFinite("maximum", value); double old = maximum; maximum = value; final Double valueObject = value; listenerList.firePropertyChange("maximum", old, valueObject); if (minimum > value) { old = minimum; minimum = value; listenerList.firePropertyChange("minimum", old, valueObject); return true; } return doubleToLongBits(value) != doubleToLongBits(old); } /** * Returns the minimal value for this graduation * * @return The minimal value in {@link #getUnit} units. * * @see #setMinimum(double) * @see #getMaximum * @see #getRange */ public double getMinimum() { return minimum; } /** * Returns the maximal value for this graduation. * * @return The maximal value in {@link #getUnit} units. * * @see #setMaximum(double) * @see #getMinimum * @see #getRange */ public double getMaximum() { return maximum; } /** * Returns the graduation's range. This is equivalents to computing * <code>{@linkplain #getMaximum} - {@linkplain #getMinimum}</code>. */ public synchronized double getRange() { return (maximum-minimum); } /** * Sets the graduation's minimum, maximum and units. This method will fire property change * events for {@code "minimum"}, {@code "maximum"} and {@code "unit"} property names. * * @param min The minimal value in the graduation. * @param max The maximal value in the graduation. * @param unit The graduation unit. */ public void setRange(final double min, final double max, final Unit<?> unit) { final Double oldMin; final Double oldMax; synchronized (this) { oldMin = minimum; oldMax = maximum; minimum = Math.min(min, max); maximum = Math.max(min, max); setUnit(unit); } listenerList.firePropertyChange("minimum", oldMin, min); listenerList.firePropertyChange("maximum", oldMax, max); } /** * Changes the graduation's units. This method will automatically convert minimum and * maximum values from the old units to the new one. * * @param newUnit The new units, or {@code null} if unknow. * If null, minimum and maximum values are not converted. * @throws ConversionException if units are not convertible. */ @Override public synchronized void setUnit(final Unit<?> newUnit) throws ConversionException { double min = minimum; double max = maximum; final Unit<?> unit = getUnit(); if (unit!=null && newUnit!=null) { final UnitConverter converter = unit.getConverterTo(newUnit); min = converter.convert(min); max = converter.convert(max); } setRange(min, max, newUnit); } /** * Returns the format to use for formatting labels. The format really used by * {@link TickIterator#currentLabel} may not be the same. For example, some * iterators may adjust automatically the number of fraction digits. */ public Format getFormat() { return NumberFormat.getNumberInstance(getLocale()); } /** * Returns an iterator object that iterates along the graduation ticks and provides access to * the graduation values. If an optional {@link RenderingHints} is specified, tick locations are * adjusted according values for {@link #VISUAL_AXIS_LENGTH} and {@link #VISUAL_TICK_SPACING} * keys. * * @param hints Rendering hints, or {@code null} for the default hints. * @param reuse An iterator to reuse if possible, or {@code null} to create a new one. A * non-null object may help to reduce the number of object garbage-collected when * rendering the axis. * @return A iterator to use for iterating through the graduation. This * iterator may or may not be the {@code reuse} object. */ public synchronized TickIterator getTickIterator(final RenderingHints hints, final TickIterator reuse) { final float visualAxisLength = getVisualAxisLength (hints); final float visualTickSpacing = getVisualTickSpacing(hints); double minimum = this.minimum; double maximum = this.maximum; if (!(minimum < maximum)) { // Uses '!' for catching NaN. minimum = (minimum+maximum)*0.5-0.5; maximum = minimum+1; } final NumberIterator it = getTickIterator(reuse, getLocale()); it.init(minimum, maximum, visualAxisLength, visualTickSpacing); return it; } /** * Constructs or reuses an iterator. This method is overridden by * {@link LogarithmicNumberGraduation}. */ NumberIterator getTickIterator(final TickIterator reuse, final Locale locale) { if (reuse!=null && reuse.getClass().equals(NumberIterator.class)) { final NumberIterator it = (NumberIterator) reuse; it.setLocale(locale); return it; } else { return new NumberIterator(locale); } } /** * Compares this graduation with the specified object for equality. * This method do not compare registered listeners. */ @Override public boolean equals(final Object object) { if (object == this) { return true; } if (super.equals(object)) { final NumberGraduation that = (NumberGraduation) object; return doubleToLongBits(this.minimum) == doubleToLongBits(that.minimum) && doubleToLongBits(this.maximum) == doubleToLongBits(that.maximum); } return false; } /** * Returns a hash value for this graduation. */ @Override public int hashCode() { final long code = doubleToLongBits(minimum) + 37 * doubleToLongBits(maximum); return (int)code ^ (int)(code >>> 32) ^ super.hashCode(); } }