/*******************************************************************************
 * Copyright (c) 2010 Haifeng Li
 *   
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package smile.plot;

import java.util.Arrays;

import smile.math.Math;

/**
 * The coordinate base of PlotCanvas. This support both 2D and 3D
 * device-independent logical space where graphics primitives are specified.
 * 

* The user need supply the lower and upper bounds for each axis. These bounds * usually are extended (a little) internally for better view purpose. * * @author Haifeng Li */ public class Base { private static final String BOUND_SIZE_DON_T_MATCH_THE_DIMENSION = "Bound size don't match the dimension."; /** * The dimensionality of base. Should be 2 or 3. */ int dimension; /** * Coordinates of base. */ double[][] baseCoords; /** * Rounded/extended lower bound of each axis. */ double[] lowerBound; /** * Rounded/extended upper bound of each axis. */ double[] upperBound; /** * Precision unit of each axis. */ private double[] precisionUnit; /** * Precision unit digits of each axis. */ private int[] precisionDigits; /** * Original lower bound of each axis. */ private double[] originalLowerBound; /** * Original upper bound of each axis. */ private double[] originalUpperBound; /** * True to round/extend bound of each axis to nearest precision units. */ private boolean[] extendBound; /** * Constructor. */ public Base(double[] lowerBound, double[] upperBound) { this(lowerBound, upperBound, true); } /** * Constructor. */ public Base(double[] lowerBound, double[] upperBound, boolean extendBound) { if (lowerBound.length != upperBound.length) { throw new IllegalArgumentException("Lower bound and upper bound size don't match."); } if (lowerBound.length != 2 && lowerBound.length != 3) { throw new IllegalArgumentException("Invalid bound dimension: " + lowerBound.length); } dimension = lowerBound.length; for (int i = 0; i < dimension; i++) { if (lowerBound[i] > upperBound[i]) { throw new IllegalArgumentException("Lower bound is larger than upper bound."); } if (lowerBound[i] == upperBound[i]) { lowerBound[i] -= 1.0; upperBound[i] += 1.0; } } this.originalLowerBound = lowerBound; this.originalUpperBound = upperBound; this.extendBound = new boolean[dimension]; Arrays.fill(this.extendBound, extendBound); precisionDigits = new int[dimension]; precisionUnit = new double[dimension]; this.lowerBound = new double[dimension]; this.upperBound = new double[dimension]; reset(); } /** * Reset base coordinates. Round/extend lower and upper bounds if necessary. */ public void reset() { for (int i = 0; i < dimension; i++) { lowerBound[i] = originalLowerBound[i]; upperBound[i] = originalUpperBound[i]; setPrecisionUnit(i); } // setup rounded base. for (int i = 0; i < dimension; i++) { if (extendBound[i]) { extendBound(i); } } initBaseCoord(); } /** * Reset coord. */ void initBaseCoord() { baseCoords = new double[dimension + 1][]; for (int i = 0; i < baseCoords.length; i++) { baseCoords[i] = lowerBound.clone(); if (i > 0) { baseCoords[i][i - 1] = upperBound[i - 1]; } } } /** * Returns the dimensionality of coordinates. */ public int getDimension() { return dimension; } /** * Set the precision unit for axis i. */ void setPrecisionUnit(int i) { if (upperBound[i] > lowerBound[i]) { double digits = Math.log10(Math.abs(upperBound[i] - lowerBound[i])); double residual = digits - Math.floor(digits); if (residual < 0.2) { // If the range is less than 15 units, we reduce one level. digits -= 1.0; } precisionDigits[i] = (int) Math.floor(digits); precisionUnit[i] = Math.pow(10, precisionDigits[i]); if (residual >= 0.2 && residual <= 0.7) { // In case of too few grids, we use a half of precision unit. precisionUnit[i] /= 2; precisionDigits[i] -= 1; } } else { precisionUnit[i] = 0.1; } } /** * Round the bounds for axis i. */ public void extendBound(int i) { if (i < 0 || i >= dimension) { throw new IllegalArgumentException("Invalid bound index: " + i); } extendBound[i] = true; lowerBound[i] = precisionUnit[i] * (Math.floor(originalLowerBound[i] / precisionUnit[i])); upperBound[i] = precisionUnit[i] * (Math.ceil(originalUpperBound[i] / precisionUnit[i])); } /** * Returns the coordinates. */ public double[][] getCoordinateSpace() { return baseCoords; } /** * Returns the lower bounds. */ public double[] getLowerBounds() { return lowerBound; } /** * Returns the upper bounds. */ public double[] getUpperBounds() { return upperBound; } /** * Returns the precision units of axes. */ public double[] getPrecisionUnit() { return precisionUnit; } /** * Returns the precision unit digits of axes. */ public int[] getPrecisionDigits() { return precisionDigits; } /** * Extend lower bounds. */ public void extendLowerBound(double[] bound) { if (bound.length != dimension) { throw new IllegalArgumentException(BOUND_SIZE_DON_T_MATCH_THE_DIMENSION); } boolean extend = false; for (int i = 0; i < bound.length; i++) { if (bound[i] < originalLowerBound[i]) { originalLowerBound[i] = bound[i]; extend = true; } } if (extend) { reset(); } } /** * Extend upper bounds. */ public void extendUpperBound(double[] bound) { if (bound.length != dimension) { throw new IllegalArgumentException(BOUND_SIZE_DON_T_MATCH_THE_DIMENSION); } boolean extend = false; for (int i = 0; i < bound.length; i++) { if (bound[i] > originalUpperBound[i]) { originalUpperBound[i] = bound[i]; extend = true; } } if (extend) { reset(); } } /** * Extend lower and upper bounds. */ public void extendBound(double[] lowerBound, double[] upperBound) { if (lowerBound.length != dimension || upperBound.length != dimension) { throw new IllegalArgumentException(BOUND_SIZE_DON_T_MATCH_THE_DIMENSION); } boolean extend = false; for (int i = 0; i < lowerBound.length; i++) { if (lowerBound[i] < this.originalLowerBound[i]) { this.originalLowerBound[i] = lowerBound[i]; extend = true; } } for (int i = 0; i < upperBound.length; i++) { if (upperBound[i] > this.originalUpperBound[i]) { this.originalUpperBound[i] = upperBound[i]; extend = true; } } if (extend) { reset(); } } @Override public String toString() { StringBuilder s = new StringBuilder(String.format("Base[%d]{", dimension)); for (int i = 0; i < baseCoords.length; i++) { s.append("["); for (int j = 0; j < baseCoords[i].length; j++) { s.append(baseCoords[i][j]).append(','); } s.setCharAt(s.length() - 1, ']'); } s.append('}'); return s.toString(); } }

Ads help maintain this website.