/**
 * Copyright 2010-2013 Jingjing Li.
 *
 * This file is part of jplot2d.
 *
 * jplot2d 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 3 of the
 * License, or any later version.
 *
 * jplot2d 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 General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jplot2d. If not, see .
 */
package org.jplot2d.axtick;

import org.jplot2d.util.NumberArrayUtils;

/**
 * 
 * @author Jingjing Li
 * 
 */
public abstract class AbstractLinearTickCalculator extends DoubleTickCalculator implements RangeAdvisor {

	/**
	 * Always be positive
	 */
	protected double interval;

	protected int minorNumber;

	protected double[] tickValues;

	protected double[] minorValues;

	/**
	 * @param start
	 * @param end
	 */
	protected void setRange(double start, double end) {
		if (Double.isNaN(start) || Double.isInfinite(start) || Double.isNaN(end) || Double.isInfinite(end)) {
			throw new IllegalArgumentException("Invalid range [" + start + "," + end + "].");
		}
		super.setRange(start, end);
	}

	/**
	 * Calculate the nice interval based on the range and proposed tick number. Besides the return value, this method
	 * also update the internal interval variable.
	 * 
	 * @param tickNumber
	 * @return
	 */
	protected DoubleInterval calcInterval(int tickNumber) {

		if (start == end) {
			throw new IllegalArgumentException("The range span must be great than zero");
		}
		if (tickNumber <= 0) {
			throw new IllegalArgumentException("The ticks number must be great than zero");
		} else if (tickNumber == 1) {
			tickNumber = 2;
		}

		double lo, hi;
		if (end > start) {
			lo = start;
			hi = end;
		} else {
			lo = end;
			hi = start;
		}
		tickNumber = Math.abs(tickNumber);

		double rough = (hi - lo) / (tickNumber - 1);
		DoubleInterval[] candidates = calcCandidateInterval(rough);
		DoubleInterval itvA = candidates[0];
		DoubleInterval itvB = candidates[1];

		double expandLo = lo - Math.abs(lo) * DOUBLE_PRECISION_TOLERANCE;
		double expandHi = hi + Math.abs(hi) * DOUBLE_PRECISION_TOLERANCE;
		long iLoA = (long) Math.ceil(expandLo / itvA.doubleValue());
		long iHiA = (long) Math.floor(expandHi / itvA.doubleValue());
		int tickNumA = (int) (iHiA - iLoA + 1);
		long iLoB = (long) Math.ceil(expandLo / itvB.doubleValue());
		long iHiB = (long) Math.floor(expandHi / itvB.doubleValue());
		int tickNumB = (int) (iHiB - iLoB + 1);

		/* tickNumB < tickNumber < tickNumA */
		if (tickNumA - tickNumber <= tickNumber - tickNumB) {
			interval = itvA.doubleValue();
			return itvA;
		} else {
			interval = itvB.doubleValue();
			return itvB;
		}

	}

	/**
	 * Calculate 2 candidate interval around the given rough interval.
	 * 
	 * @param rough
	 *            the rough interval
	 * @return a array contains 2 DoubleInterval objects.
	 */
	protected abstract DoubleInterval[] calcCandidateInterval(double rough);

	public void calcValuesByTickInterval(double interval, double offset, int minorTickNumber) {
		interval = Math.abs(interval);

		if (minorTickNumber == AUTO_MINORTICK_NUMBER) {
			minorNumber = calcMinorNumber(interval);
		} else {
			minorNumber = minorTickNumber;
		}

		calcValues(interval, offset, minorNumber);
	}

	/**
	 * Calculate a proper minor ticks number for AUTO_MINORTICK_NUMBER
	 * 
	 * @param interval
	 * @return
	 */
	protected abstract int calcMinorNumber(double interval);

	/**
	 * @param interval
	 *            must be positive
	 * @param offset
	 * @param minorTickNumber
	 */
	protected void calcValues(double interval, double offset, int minorTickNumber) {

		if (interval == 0 || Double.isNaN(interval))
			throw new IllegalArgumentException("delta cannot be zero or NaN");

		boolean inverted = start > end;

		double lo, hi;
		if (!inverted) {
			lo = start;
			hi = end;
		} else {
			lo = end;
			hi = start;
		}
		double d = interval;

		double expandLo = lo - Math.abs(lo) * DOUBLE_PRECISION_TOLERANCE - offset;
		double expandHi = hi + Math.abs(hi) * DOUBLE_PRECISION_TOLERANCE - offset;
		long iLo = (long) Math.ceil(expandLo / d);
		long iHi = (long) Math.floor(expandHi / d);
		int tickNum = (int) (iHi - iLo + 1);
		tickValues = new double[tickNum];

		double minorInterval = d / (minorTickNumber + 1);

		if (tickNum == 0) {
			long miLo = (long) Math.ceil(expandLo / minorInterval);
			long miHi = (long) Math.floor(expandHi / minorInterval);
			int mTickNum = (int) (miHi - miLo + 1);
			minorValues = new double[mTickNum];
			int mvi = 0;
			for (long mi = miLo; mi <= miHi; mi++) {
				minorValues[mvi++] = minorInterval * mi + offset;
			}
		} else {
			double lowMargine = -expandLo + d * iLo;
			double hiMargine = expandHi - d * iHi;
			int minorNumBeforeLow = (int) (lowMargine / minorInterval);
			int minorNumAfterHi = (int) (hiMargine / minorInterval);
			minorValues = new double[(minorNumBeforeLow + (tickNum - 1) * minorTickNumber + minorNumAfterHi)];
			int mvi = 0;
			int tvi = 0;
			double v = d * iLo + offset;
			for (int im = minorNumBeforeLow; im > 0; im--) {
				minorValues[mvi++] = v - minorInterval * im;
			}
			for (long i = iLo;;) {
				v = d * i + offset;
				tickValues[tvi++] = v;
				if (i++ == iHi) {
					break;
				}
				for (int im = 1; im <= minorTickNumber; im++) {
					minorValues[mvi++] = v + minorInterval * im;
				}
			}
			for (int im = 1; im <= minorNumAfterHi; im++) {
				minorValues[mvi++] = v + minorInterval * im;
			}

			if (inverted) {
				tickValues = NumberArrayUtils.reverse(tickValues);
				minorValues = NumberArrayUtils.reverse(minorValues);
			}
		}
	}

	public void expandRangeByTickNumber(int tickNumber) {

		if (tickNumber == 0) {
			throw new IllegalArgumentException("nticks cannot be zero");
		} else if (tickNumber == 1) {
			tickNumber = 2;
		}

		double lo, hi;
		if (end > start) {
			lo = start;
			hi = end;
		} else {
			lo = end;
			hi = start;
		}
		tickNumber = Math.abs(tickNumber);

		double rough = (hi - lo) / (tickNumber - 1);
		DoubleInterval[] candidates = calcCandidateInterval(rough);
		DoubleInterval itvA = candidates[0];
		DoubleInterval itvB = candidates[1];

		double shrinkLo = lo + Math.abs(lo) * DOUBLE_PRECISION_TOLERANCE;
		double shrinkHi = hi - Math.abs(hi) * DOUBLE_PRECISION_TOLERANCE;
		long iLoA = (long) Math.floor(shrinkLo / itvA.doubleValue());
		long iHiA = (long) Math.ceil(shrinkHi / itvA.doubleValue());
		int tickNumA = (int) (iHiA - iLoA + 1);
		long iLoB = (long) Math.floor(shrinkLo / itvB.doubleValue());
		long iHiB = (long) Math.ceil(shrinkHi / itvB.doubleValue());
		int tickNumB = (int) (iHiB - iLoB + 1);

		double xLo, xHi;
		/* tickNumB < tickNumber < tickNumA */
		if (tickNumA - tickNumber <= tickNumber - tickNumB) {
			interval = itvA.doubleValue();
			xLo = interval * iLoA;
			xHi = interval * iHiA;
		} else {
			interval = itvB.doubleValue();
			xLo = interval * iLoB;
			xHi = interval * iHiB;
		}

		if (end > start) {
			start = xLo;
			end = xHi;
		} else {
			start = xHi;
			end = xLo;
		}

	}

	public void expandRangeByTickInterval(double interval) {

		double lo, hi;
		if (end > start) {
			lo = start;
			hi = end;
		} else {
			lo = end;
			hi = start;
		}
		double d = Math.abs(interval);

		double shrinkLo = lo + Math.abs(lo) * DOUBLE_PRECISION_TOLERANCE;
		double shrinkHi = hi - Math.abs(hi) * DOUBLE_PRECISION_TOLERANCE;
		long iLo = (long) Math.floor(shrinkLo / d);
		long iHi = (long) Math.ceil(shrinkHi / d);
		double xLo = d * iLo;
		double xHi = d * iHi;

		interval = d;
		if (end > start) {
			start = xLo;
			end = xHi;
		} else {
			start = xHi;
			end = xLo;
		}

	}

	@Override
	public double getInterval() {
		return interval;
	}

	@Override
	public int getMinorNumber() {
		return minorNumber;
	}

	@Override
	public double[] getValues() {
		return tickValues;
	}

	@Override
	public double[] getMinorValues() {
		return minorValues;
	}

}

 

Ads help maintain this website.