/* * Axis.java * * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST 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; either version 2 * of the License, or (at your option) any later version. * * BEAST 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. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.app.gui.chart; import dr.util.NumberFormatter; /** * Axis.java * * Description: Provides an interface for axis * @author Andrew Rambaut * @version $Id: Axis.java,v 1.11 2005/05/24 20:25:59 rambaut Exp $ */ public interface Axis { // These constants are used for automatic scaling to select exactly // where the axis starts and stops. static public final int AT_MAJOR_TICK=0; static public final int AT_MAJOR_TICK_PLUS=1; static public final int AT_MINOR_TICK=2; static public final int AT_MINOR_TICK_PLUS=3; static public final int AT_DATA=4; static public final int AT_ZERO=5; static public final int AT_VALUE=6; static public final int AT_MAJOR_TICK_MINUS=7; // offset towards negatives, especially to raise 0 in y /** * Set axis flags */ void setAxisFlags(int minAxisFlag, int maxAxisFlag); /** * Set preferred number of ticks */ void setPrefNumTicks(int prefNumMajorTicks, int prefNumMinorTicks); /** * Set integer scale */ void setIsDiscrete(boolean isDiscrete); /** * Set show label for first tick */ void setLabelFirst(boolean labelFirst); /** * Set show label for last tick */ void setLabelLast(boolean labelLast); /** *@return IsDiscrete */ boolean getIsDiscrete(); /** *@return show label for first tick */ boolean getLabelFirst(); /** *@return show label for last tick */ boolean getLabelLast(); /** * Manually set the axis range. Axis flags must be set to AT_VALUE for this to take effect. */ void setManualRange(double minValue, double maxValue); /** * Manually set the axis range and ticks */ void setManualAxis(double minTick, double maxTick, double majorTick, double minorTick); /** * Set the axis to automatic calibration */ void setAutomatic(); /** * Set the axis to automatic calibration */ public void setAutomatic(int minAxisFlag, int maxAxisFlag); /** * Set the range of the data */ public void setRange(double minValue, double maxValue); /** * Adds the range of the data */ public void addRange(double minValue, double maxValue); /** * Transform a value */ public double transform(double value); /** * Untransform a value */ public double untransform(double value); /** * @return a string that appropriately formats the value */ public String format(double value); /** * @return minimum range of the axis */ public double getMinAxis(); /** * @return maximum range of the axis */ public double getMaxAxis(); /** * @return minimum range of the data */ public double getMinData(); /** * @return maximum range of the data */ public double getMaxData(); /** * @return the number of major tick marks along the axis */ public int getMajorTickCount(); /** * @return the number of minor tick marks within each major one * By default all major ticks have the same number of minor ticks * except the last which has none. */ public int getMinorTickCount(int majorTickIndex); /** * @return the value of the majorTickIndex'th major tick */ public double getMajorTickValue(int majorTickIndex); /** * @return the value of the minorTickIndex'th minor tick */ public double getMinorTickValue(int minorTickIndex, int majorTickIndex); /** * @return the spacing between major ticks */ public double getMajorTickSpacing(); /** * @return the spacing between minor ticks */ public double getMinorTickSpacing(); /** class AbstractAxis * This class provides a base class for all axis. */ public abstract class AbstractAxis implements Axis { // The minimum and maximum values of the data protected double minData=Double.POSITIVE_INFINITY, maxData=Double.NEGATIVE_INFINITY; // The number of major ticks and minor ticks within them protected int majorTickCount, minorTickCount; // calculated automatically // The prefered minimum number of ticks protected int prefMajorTickCount = 5, prefMinorTickCount = 2; // set manually // Flags using the above constants protected int minAxisFlag, maxAxisFlag; // The distance between major ticks and minor Ticks protected double majorTick, minorTick; // calculated automatically or set by user // The value of the first and last major tick protected double minTick, maxTick; // calculated automatically or set by user // The value of the beginning and end of the axis protected double minAxis, maxAxis; // User defined axis range protected double minValue, maxValue; // Flags to give automatic scaling and integer division protected boolean isAutomatic=true, isDiscrete=false; // Flags to specify that the first tick and last tick should have labels. // It is up to the AxisPanel to do something about this. protected boolean labelFirst=false, labelLast=false; protected boolean isCalibrated = false; protected final NumberFormatter formatter = new NumberFormatter(8); // Used internally private double epsilon; private int fraction; /** * The constructor for automatic calibration */ public AbstractAxis() { this(AT_MAJOR_TICK, AT_MAJOR_TICK, false); } /** * The constructor for automatic calibration */ public AbstractAxis(int minAxisFlag, int maxAxisFlag) { this(minAxisFlag, maxAxisFlag, false); } /** * The constructor for automatic calibration */ public AbstractAxis(int minAxisFlag, int maxAxisFlag, boolean isDiscrete) { this.minAxisFlag = minAxisFlag; this.maxAxisFlag = maxAxisFlag; this.isDiscrete = isDiscrete; isAutomatic = true; isCalibrated = false; } /** * The constructor for manual calibration */ public AbstractAxis(double minTick, double maxTick, double majorTick, double minorTick) { setManualAxis(minTick, maxTick, majorTick, minorTick); } /** * Set axis flags */ public void setAxisFlags(int minAxisFlag, int maxAxisFlag) { this.minAxisFlag = minAxisFlag; this.maxAxisFlag = maxAxisFlag; isCalibrated = false; } /** * Set preferred number of ticks */ public void setPrefNumTicks(int prefMajorTickCount, int prefMinorTickCount) { this.prefMajorTickCount = prefMajorTickCount; this.prefMinorTickCount = prefMinorTickCount; isCalibrated = false; } /** * Set integer scale */ public void setIsDiscrete(boolean isDiscrete) { this.isDiscrete = isDiscrete; isCalibrated = false; } /** * Set show label for first tick flag */ public void setLabelFirst(boolean labelFirst) { this.labelFirst = labelFirst; } /** * Set show label for last tick flag */ public void setLabelLast(boolean labelLast) { this.labelLast = labelLast; } /** * Set the number of significant figures for the tick labels */ public void setSignficantFigures(int sf) { this.formatter.setSignificantFigures(sf); } public String format(double value) { return formatter.format(value); } /** * return show label for first tick flag */ public boolean getIsDiscrete() { return this.isDiscrete; } /** * return show label for first tick flag */ public boolean getLabelFirst() { return getMinorTickCount(-1) != 0 && labelFirst; } /** * return show label for last tick flag */ public boolean getLabelLast() { return getMinorTickCount(majorTickCount - 1) != 0 && labelLast; } /** * Manually set the axis range. Axis flags must be set to AT_VALUE for this to take effect. */ public void setManualRange(double minValue, double maxValue) { if (!Double.isInfinite(minValue) && !Double.isNaN(minValue)) { this.minValue = minValue; } if (!Double.isInfinite(minValue) && !Double.isNaN(minValue)) { this.maxValue = maxValue; } isCalibrated = false; } /** * Manually set the axis ticks */ public void setManualAxis(double minTick, double maxTick, double majorTick, double minorTick) { this.minTick = minTick; this.maxTick = maxTick; this.majorTick = majorTick; this.minorTick = minorTick; majorTickCount = (int)((maxTick-minTick)/majorTick)+1; // Add 1 to include the last tick minorTickCount = (int)(majorTick/minorTick)-1; // Sub 1 to exclude the major tick isAutomatic=false; isCalibrated = false; } /** * Set the axis to automatic calibration */ public void setAutomatic() { setAutomatic(AT_MAJOR_TICK, AT_MAJOR_TICK); } /** * Set the axis to automatic calibration */ public void setAutomatic(int minAxisFlag, int maxAxisFlag) { setAxisFlags(minAxisFlag, maxAxisFlag); isAutomatic = true; isCalibrated = false; } /** * Set the range of the data */ public void setRange(double minValue, double maxValue) { if (!Double.isNaN(minValue)) { this.minData = minValue; } if (!Double.isNaN(maxValue)) { this.maxData = maxValue; } isCalibrated = false; } /** * Adds the range to the existing range, widening if neccessary */ public void addRange(double minValue, double maxValue) { if (!Double.isNaN(minValue) && maxValue > maxData) { maxData = maxValue; } if (!Double.isNaN(maxValue) && minValue < minData) { minData = minValue; } //System.err.println("addRange("+minValue +", "+maxValue+")"); //System.err.println("maxValue = "+maxData); //System.err.println("maxData = "+maxData); isCalibrated = false; } /** * A static method that uses the natural log to obtain log to base10. * This is required for the linear autoCalibrate but will also be * used by a derived class giving a log transformed axis. */ static public double log10(double inValue) { return Math.log(inValue)/Math.log(10.0); } /** autoCalibrate * Attempt to find the optimum axis range and ticks. * This will attempt to use at least numMajorTick ticks on the axis. */ static private final int UNIT=0; static private final int HALFS=1; static private final int QUARTERS=2; static private final int FIFTHS=3; public void calibrate() { double minValue = minData; double maxValue = maxData; if( Double.isInfinite(minValue) || Double.isNaN(minValue) || Double.isInfinite(maxValue) || Double.isNaN(maxValue)) { // I am not sure which exception is appropriate here. throw new ChartRuntimeException("Illegal range values, can't calibrate"); } if (minAxisFlag==AT_ZERO ) { minValue = 0; } else if (minAxisFlag == AT_VALUE) { minValue = this.minValue; } if (maxAxisFlag==AT_ZERO) { maxValue = 0; } else if (maxAxisFlag == AT_VALUE) { maxValue = this.maxValue; } double range = maxValue - minValue; if (range < 0.0) { range = 0.0; } epsilon = range * 1.0E-10; if (isAutomatic) { // We must find the optimum minMajorTick and maxMajorTick so // that they contain the data range (minData to maxData) and // are in the right order of magnitude if (range < 1.0E-30) { if (minData < 0.0) { majorTick = Math.pow(10.0, Math.floor(log10(Math.abs(minData)))); minTick = Math.floor(minData / majorTick) * majorTick; maxTick = 0.0; } else if (minData > 0.0) { majorTick = Math.pow(10.0, Math.floor(log10(Math.abs(minData)))); minTick = 0.0; maxTick = Math.ceil(maxData / majorTick) * majorTick; } else { minTick = -1.0; maxTick = 1.0; majorTick = 1.0; } minorTick = majorTick; majorTickCount = 1; minorTickCount = 0; } else { // First find order of magnitude below the data range... majorTick = Math.pow(10.0, Math.floor(log10(range))); calcMinTick(); calcMaxTick(); calcMajorTick(); calcMinorTick(); } } minAxis = minTick; maxAxis = maxTick; handleAxisFlags(); isCalibrated=true; } /** * Calculate the optimum minimum tick. Override to change default behaviour */ public void calcMinTick() { // Find the nearest multiple of majorTick below minData if (minData == 0.0) minTick = 0; else minTick = Math.floor(minData / majorTick) * majorTick; } /** * Calculate the optimum maximum tick. Override to change default behaviour */ public void calcMaxTick() { // Find the nearest multiple of majorTick above maxData if (maxData == 0) { maxTick = 0; } else if (maxData < 0.0) { // Added so that negative values are handled correctly -- AJD maxTick = -Math.floor(-maxData / majorTick) * majorTick; } else { maxTick = Math.ceil(maxData / majorTick) * majorTick; } } /** * Calculate the optimum major tick distance. Override to change default behaviour */ public void calcMajorTick() { fraction=UNIT; // make sure that there are at least prefNumMajorTicks major ticks // by dividing up into halves, quarters, fifths or tenths double u=majorTick; double r=maxTick-minTick; majorTickCount=(int)(r/u); while (majorTickCount < prefMajorTickCount) { u=majorTick/2; // Try using halves if (!isDiscrete || u==Math.floor(u)) { // u is an integer majorTickCount=(int)(r/u); fraction=HALFS; if (majorTickCount >= prefMajorTickCount) break; } u=majorTick/4; // Try using quarters if (!isDiscrete || u==Math.floor(u)) { // u is an integer majorTickCount=(int)(r/u); fraction=QUARTERS; if (majorTickCount >= prefMajorTickCount) break; } u=majorTick/5; // Try using fifths if (!isDiscrete || u==Math.floor(u)) { // u is an integer majorTickCount=(int)(r/u); fraction=FIFTHS; if (majorTickCount >= prefMajorTickCount) break; } if (isDiscrete && (majorTick/10)!=Math.floor(majorTick/10)) { // majorTick/10 is not an integer so no point in further subdivision u=majorTick; majorTickCount=(int)(r/u); break; } majorTick/=10; // finally just divide by ten u=majorTick; // and go back to whole units majorTickCount=(int)(r/u); fraction=UNIT; } majorTick=u; if (isDiscrete && majorTick<1.0) { majorTick=1.0; majorTickCount=(int)(r/majorTick); fraction=UNIT; } majorTickCount++; // Add 1 to give the final tick // Trim down any excess major ticks either side of the data range // Epsilon allows for any inprecision in the calculation while ((minTick + majorTick - epsilon)<minData) { minTick+=majorTick; majorTickCount--; } while ((maxTick - majorTick + epsilon)>maxData) { maxTick-=majorTick; majorTickCount--; } } /** * Calculate the optimum minor tick distance. Override to change default behaviour */ public void calcMinorTick() { minorTick=majorTick; // start with minorTick the same as majorTick double u=minorTick; double r=majorTick; minorTickCount=(int)(r/u); while (minorTickCount < prefMinorTickCount) { // if the majorTick was divided as quarters, then we can't // divide the minor ticks into halves or quarters. if (fraction!=QUARTERS) { u=minorTick/2; // Try using halves if (!isDiscrete || u==Math.floor(u)) { // u is an integer minorTickCount=(int)(r/u); if (minorTickCount>=prefMinorTickCount) break; } u=minorTick/4; // Try using quarters if (!isDiscrete || u==Math.floor(u)) { // u is an integer minorTickCount=(int)(r/u); if (minorTickCount>=prefMinorTickCount) break; } } u=minorTick/5; // Try using fifths if (!isDiscrete || u==Math.floor(u)) { // u is an integer minorTickCount=(int)(r/u); if (minorTickCount>=prefMinorTickCount) break; } if (isDiscrete && (minorTick/10)!=Math.floor(minorTick/10)) { // minorTick/10 is not an integer so no point in further subdivision u=minorTick; minorTickCount=(int)(r/u); break; } minorTick/=10; // finally just divide by ten u=minorTick; // and go back to whole units minorTickCount=(int)(r/u); } minorTick=u; minorTickCount--; } /** * Handles axis flags. Override to change default behaviour */ public void handleAxisFlags() { // Now we must honor the min/maxAxisFlag settings if (minAxisFlag==AT_MAJOR_TICK_PLUS || minAxisFlag==AT_MINOR_TICK_PLUS) { if (minAxis==minData) { majorTickCount++; // minTick-=majorTick; minAxis=minTick; } } if (minAxisFlag==AT_MAJOR_TICK_MINUS) { if (minAxis==minData) { majorTickCount++; minTick-=majorTick; minAxis=minTick; } } if (minAxisFlag==AT_MINOR_TICK_PLUS) { if ((minAxis+minorTick)<minData) { majorTickCount--; minTick+=majorTick; while ((minAxis+minorTick)<minData) { minAxis+=minorTick; } } } else if (minAxisFlag==AT_MINOR_TICK) { if ((minAxis+minorTick)<=minData) { majorTickCount--; minTick+=majorTick; while ((minAxis+minorTick)<=minData) { minAxis+=minorTick; } } } else if (minAxisFlag==AT_DATA) { if (minTick<minData) { // in case minTick==minData majorTickCount--; minTick+=majorTick; } minAxis=minData; } else if (minAxisFlag==AT_VALUE) { if (minTick<minValue) { // in case minTick==minValue majorTickCount--; minTick+=majorTick; } minAxis=minValue; } else if (minAxisFlag==AT_ZERO) { majorTickCount+=(int)(minTick/majorTick); minTick=0; minAxis=0; } if (maxAxisFlag==AT_MAJOR_TICK_PLUS || maxAxisFlag==AT_MINOR_TICK_PLUS) { if (maxAxis==maxData) { majorTickCount++; maxTick+=majorTick; maxAxis=maxTick; } } if (maxAxisFlag==AT_MINOR_TICK_PLUS) { if ((maxAxis-minorTick)>maxData) { majorTickCount--; maxTick-=majorTick; while ((maxAxis-minorTick)>maxData) { maxAxis-=minorTick; } } } else if (maxAxisFlag==AT_MINOR_TICK) { if ((maxAxis-minorTick)>=maxData) { majorTickCount--; maxTick-=majorTick; while ((maxAxis-minorTick)>=maxData) { maxAxis-=minorTick; } } } else if (maxAxisFlag==AT_DATA) { if (maxTick>maxData) { // in case maxTick==maxData majorTickCount--; maxTick-=majorTick; } maxAxis=maxData; } else if (maxAxisFlag==AT_VALUE) { if (maxTick>maxValue) { // in case maxTick==maxValue majorTickCount--; maxTick-=majorTick; } maxAxis=maxValue; } else if (maxAxisFlag==AT_ZERO) { majorTickCount+=(int)(-maxTick/majorTick); maxTick=0; maxTick=0; } } /** * Scale a value to between 0 and 1. */ public double scaleValue(double value) { if (!isCalibrated) calibrate(); final double ta = transform(minAxis); return (transform(value)- ta)/(transform(maxAxis)- ta); } /** * @return minimum range of the axis */ public double getMinAxis() { if (!isCalibrated) calibrate(); return minAxis; } /** * @return maximum range of the axis */ public double getMaxAxis() { if (!isCalibrated) calibrate(); return maxAxis; } /** * @return minimum range of the data */ public double getMinData() { return minData; } /** * @return maximum range of the data */ public double getMaxData() { return maxData; } /** * Returns the number of major tick marks along the axis */ public int getMajorTickCount() { if (!isCalibrated) calibrate(); return majorTickCount; } /** * Returns the number of minor tick marks within each major one * By default all major ticks have the same number of minor ticks * except the last which has none. */ public int getMinorTickCount(int majorTickIndex) { if (!isCalibrated) calibrate(); if (majorTickIndex == majorTickCount-1) return (int)((maxAxis-maxTick)/minorTick); else if (majorTickIndex==-1) return (int)((minTick-minAxis)/minorTick); else return minorTickCount; } /** getMajorTick * Returns the value of the majorTickIndex'th major tick */ public double getMajorTickValue(int majorTickIndex) { if (!isCalibrated) calibrate(); return (majorTickIndex*majorTick)+minTick; } /** getMinorTick * Returns the value of the minorTickIndex'th minor tick */ public double getMinorTickValue(int minorTickIndex, int majorTickIndex) { if (!isCalibrated) calibrate(); // get minorTickIndex+1 to skip the major tick if (majorTickIndex==-1) return minTick-((minorTickIndex+1)*minorTick); else return ((minorTickIndex+1)*minorTick)+getMajorTickValue(majorTickIndex); } /** * @return the spacing between major ticks */ public double getMajorTickSpacing() { if (!isCalibrated) calibrate(); return majorTick; } /** * @return the spacing between minor ticks */ public double getMinorTickSpacing() { if (!isCalibrated) calibrate(); return minorTick; } } }