//
// AxisScale.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package visad;

import java.awt.Color;
import java.awt.Font;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;

import visad.util.HersheyFont;

/**
 * Class which defines the scales displayed along the spatial axes
 * of a display.  Each ScalarMap that has a DisplayScalar of the
 * X, Y, or Z axis will have a non-null AxisScale.
 * @see ScalarMap#getAxisScale()
 * @author Don Murray
 */
public class AxisScale implements java.io.Serializable
{

  private static final long serialVersionUID = 1L;
  /** X_AXIS identifier */
  public static final int X_AXIS = 0;
  /** Y_AXIS identifier */
  public static final int Y_AXIS = 1;
  /** Z_AXIS identifier */
  public static final int Z_AXIS = 2;
  /** identifier for primary label side of axis*/
  public static final int PRIMARY = 0;
  /** identifier for secondary label side of axis*/
  public static final int SECONDARY = 1;
  /** identifier for tertiary label side of axis*/
  public static final int TERTIARY = 2;
  /** identifier for quaternary label side of axis*/
  public static final int QUATERNARY = 3;

  // WLH 12 July 2001
  // true indicates axis is stationary relative to screen
  // rather than graphics coordinates
  private boolean screenBased = false;
  private boolean gridLinesVisible = false;
  private boolean ticksVisible = true;
  private boolean labelBothSides = false;

  private VisADLineArray scaleArray;
  private VisADTriangleArray labelArray;
  private ScalarMap scalarMap;
  private Color myColor = Color.white;
  private double[] dataRange = new double[2];
  private int myAxis = -1;
  private int axisOrdinal = -1;
  private String myTitle;
  private Hashtable labelTable;
  private double[] majorTicks = null;
  private double[] minorTicks = null;
  private double majorTickSpacing = 0.0;
  private double minorTickSpacing = 0.0;
  private double tickBase = 0.0;
  private boolean autoComputeTicks = true;
  private boolean baseLineVisible = true;
  private boolean snapToBox = false;
  private boolean userLabels = false;
  private boolean visibility = true;
  private boolean labelAllTicks = false;  // label major ticks
  private Object labelFont = null;
  private int labelSize = 12;
  private int axisSide = PRIMARY;
  private int tickOrient = PRIMARY;
  private static final double TICKSIZE = .5;  // major ticks are 1/2 char ht.
  private NumberFormat labelFormat = null;
  
  /** Is the label angled away from the axis or is it "flat"*/
  private boolean labelRelief = true;

  /**
   * Construct a new AxisScale for the given ScalarMap
   * @param map  ScalarMap to monitor.  Must be mapped to one of
   *       Display.XAxis, Display.YAxis, Display.ZAxis
   * @throws  VisADException  bad ScalarMap or other VisAD problem
   */
  public AxisScale(ScalarMap map)
    throws VisADException
  {
    scalarMap = map;
    DisplayRealType displayScalar = scalarMap.getDisplayScalar();
    if (!displayScalar.equals(Display.XAxis) &&
      !displayScalar.equals(Display.YAxis) &&
      !displayScalar.equals(Display.ZAxis)) {
        throw new DisplayException("AxisSale: DisplayScalar " +
                                   "must be XAxis, YAxis or ZAxis");
    }
    myAxis = (displayScalar.equals(Display.XAxis)) ? X_AXIS :
       (displayScalar.equals(Display.YAxis)) ? Y_AXIS : Z_AXIS;
    myTitle = scalarMap.getScalarName();
    visibility = scalarMap.getScaleEnable();
    labelTable = new Hashtable();
    DisplayImpl display = scalarMap.getDisplay();
    if (display != null) {
      DisplayRenderer displayRenderer = display.getDisplayRenderer();
      if (displayRenderer != null) {
        float[] rgb = displayRenderer.getRendererControl().getForegroundColor();
        myColor = new Color(rgb[0], rgb[1], rgb[2]);
        boolean ok = makeScale();
      }
    }
  }

  /**
   * Get the position of this AxisScale on the Axis (first, second, third).
   *
   * @return  position from the axis (first = 0, second = 1, etc)
   */
  public int getAxisOrdinal()
  {
    return axisOrdinal;
  }

  /**
   * Set the position of this AxisScale on the axis.  Should only
   * be called by ScalarMap
   * @param  ordinalValue  axis position (0 = first, 1 = second, etc)
   */
  void setAxisOrdinal(int ordinalValue)
  {
    axisOrdinal = ordinalValue;
  }

  /**
   * @deprecated
   * Set the label to be used for this axis.  The default is the
   * ScalarName of the ScalarMap.
   * @param  label  label to be used
   * @see #setTitle(String)
   */
  public void setLabel(String label)
  {
    setTitle(label);
  }

  /**
   * @deprecated
   * Get the label of the AxisScale.
   * @return label
   * @see #getTitle()
   */
  public String getLabel()
  {
    return getTitle();
  }

  /**
   * Set the title to be used for this axis.  The default is the
   * ScalarName of the ScalarMap.
   * @param  title  title to be used
   */
  public void setTitle(String title)
  {
    String oldTitle = myTitle;
    myTitle = title;
    if (!myTitle.equals(oldTitle) ) {
      try {
        // check for case where this was called from scalarmap.setScalarName()
        if ( !myTitle.equals(scalarMap.getScalarName()) )
        {
          scalarMap.setScalarName(myTitle);
        }
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Get the title of the AxisScale.
   * @return title
   */
  public String getTitle()
  {
    return myTitle;
  }

  /**
   * Get axis that the scale will be displayed on.
   * @return  axis  (X_AXIS, Y_AXIS or Z_AXIS)
   */
  public int getAxis()
  {
    return myAxis;
  }

  /**
   * Get the Scale to pass to the renderer.
   * @return  VisADLineArray representing the scale
   */
  public VisADLineArray getScaleArray()
  {
    return scaleArray;
  }

  /**
   * Get the labels rendered with a font to pass to the renderer.
   * @return  VisADTriangleArray representing the labels
   */
  public VisADTriangleArray getLabelArray()
  {
    return labelArray;
  }

  /**
   * set screenBased mode
   *   true indicates axis is stationary relative to screen
  */
  public void setScreenBased(boolean sb) {
    screenBased = sb;
  }

  /**
   * return screenBased mode
   * @return  true if axis is stationary relative to screen
  */
  public boolean getScreenBased() {
    return screenBased;
  }

  /**
   * Create the scale for screen based.
   * @return  true if scale was successfully created, otherwise false
   */
  public boolean makeScreenBasedScale(double xmin, double ymin,
                                      double xmax, double ymax,
                                      double XTMIN, double YTMIN,
                                      double XTMAX, double YTMAX)
         throws VisADException {
    DisplayImpl display = scalarMap.getDisplay();
    if (display == null) return false;
    DisplayRenderer displayRenderer = display.getDisplayRenderer();
    if (displayRenderer == null) return false;
    if (axisOrdinal < 0) return false;
    dataRange = scalarMap.getRange();
    // boolean twoD = displayRenderer.getMode2D();
    if (!displayRenderer.getMode2D()) return false;
    boolean twoD = true;

    ProjectionControl pcontrol = display.getProjectionControl();
    double[] aspect = pcontrol.getAspectCartesian();
    double oldMax = 1.0;
    double oldMin = -1.0;
    double newMax = 1.0;
    double newMin = -1.0;
    if (myAxis == X_AXIS) {
      oldMax = aspect[0];
      oldMin = -aspect[0];
      newMax = XTMAX;
      newMin = XTMIN;
    }
    else if (myAxis == Y_AXIS) {
      oldMax = aspect[1];
      oldMin = -aspect[1];
      newMax = YTMAX;
      newMin = YTMIN;
    }

    double mult = (dataRange[1] - dataRange[0]) / (oldMax - oldMin);
    double d1 = (newMax - oldMin) * mult + dataRange[0];
    double d0 = (newMin - oldMin) * mult + dataRange[0];
    double[] dr = {d0, d1};

    double zmin = 0.0;
    double zmax = -zmin;

    // set scale according to labelSize
    double scale =  labelSize/200.;
    double offset = 1.05;

    // WLH 20 Feb 2003 keep screen-based YAxis label in Frame
    scale *= 0.6; // hack size for screen based
    // scale *= 0.8; // hack size for screen based

    // Add 16-APR-2001 DRM
    int position = 0;
    int myPosition = 0;
    // Snap to the box edge instead of being offset
    if (snapToBox) {
      offset = 1.0;
    }
    else
    {
      for (Enumeration e = display.getMapVector().elements();
            e.hasMoreElements();)
      {
        ScalarMap map = (ScalarMap) e.nextElement();
        if (map.getDisplayScalar().equals(scalarMap.getDisplayScalar()))
        {
          if (getSide() == map.getAxisScale().getSide()) 
          {
            if (!map.equals(scalarMap)) // same side someone else
            {
              position++;
            } else {
              myPosition = position;
            }
          }
        }
      }
    }
    /*
    System.out.println(scalarMap + "is at position " + (myPosition+1) +
                       " out of " + (position + 1));
    */
    // End Add 16-APR-2001 DRM

    // position of baseline for this scale
    double line = 4.0 * myPosition * scale;  // DRM 17-APR-2001

    return makeScale(twoD, xmin, ymin, zmin, xmax, ymax, zmax,
                     scale, offset, line, dr);
  }

  /**
   * Create the scale.
   * @return  true if scale was successfully created, otherwise false
   */
  public boolean makeScale()
      throws VisADException {
    DisplayImpl display = scalarMap.getDisplay();
    if (display == null) return false;
    DisplayRenderer displayRenderer = display.getDisplayRenderer();
    if (displayRenderer == null) return false;
    if (axisOrdinal < 0) {
      axisOrdinal = displayRenderer.getAxisOrdinal(myAxis);
    }
    dataRange = scalarMap.getRange();
    boolean twoD = displayRenderer.getMode2D();

  // now create scale along axis at axisOrdinal position in array
  // twoD may help define orientation

// WLH 24 Nov 2000
    ProjectionControl pcontrol = display.getProjectionControl();
    double[] aspect = pcontrol.getAspectCartesian();

    double xmin = -aspect[0];
    double ymin = -aspect[1];
    double zmin = -aspect[2];

    double xmax = -xmin;
    double ymax = -ymin;
    double zmax = -zmin;

    // set scale according to labelSize
    double scale =  labelSize/200.;
    double offset = 1.05;

    // Add 16-APR-2001 DRM
    int position = 0;
    int myPosition = 0;
    // Snap to the box edge instead of being offset
    if (snapToBox) {
      offset = 1.0;
    }
    else
    {
      for (Enumeration e = display.getMapVector().elements();
            e.hasMoreElements();)
      {
        ScalarMap map = (ScalarMap) e.nextElement();
        if (map.getDisplayScalar().equals(scalarMap.getDisplayScalar()))
        {
          if (getSide() == map.getAxisScale().getSide()) {
            if (!map.equals(scalarMap)) // same side someone else
            {
              position++;
            } else {
              myPosition = position;// this is me
            }
          }
        }
      }
    }
    /*
    System.out.println(scalarMap + "is at position " + (myPosition+1) +
                       " out of " + (position + 1));
    */
    // End Add 16-APR-2001 DRM

    // position of baseline for this scale
    double line = 4.0 * myPosition * scale;  // DRM 17-APR-2001

    /*  Remove 16-APR-2001
    double one = 1.0;
    if (dataRange[0] > dataRange[1]) one = -1.0; // inverted range
    int position = axisOrdinal;
    if (snapToBox) {
      offset = 1.0;
      position = 0;
    }
    double line = 2.0 * position * scale;
    */


    return makeScale(twoD, xmin, ymin, zmin, xmax, ymax, zmax,
                     scale, offset, line, dataRange);
  }

  /** inner logic of makeScale with no references to display, displayRenderer
      or scalarMap, allwoing more flexible placement of scales */
  public boolean makeScale(boolean twoD, double xmin, double ymin, double zmin,
                           double xmax, double ymax, double zmax,
                           double scale, double offset, double line,
                           double[] dataRange)
         throws VisADException {

// start new method here
// no references to display, scalarMap, or displayRenderer past this point


    // compute graphics positions
    // these are {x, y, z} vectors
    double[] base = null; // vector from one character to another
    double[] up = null; // vector from bottom of character to top
    double[] startn = null; // -1.0 position along axis
    double[] startp = null; // +1.0 position along axis
    double[] gridstartn = null; // -1.0 position along axis
    double[] gridstartp = null; // +1.0 position along axis

    int numSides = (getLabelBothSides()) ? 2 : 1;
    Vector lineArrayVector = new Vector(4*numSides);
    Vector labelArrayVector = new Vector();

    double one = 1.0;
    if (dataRange[0] > dataRange[1]) one = -1.0; // inverted range

    for (int l = 0; l < numSides; l++) {
      int side = getSide();
      side = (side + l) % (twoD ? 2 : 4);
      // set up the defaults for each of the axes.  startp and startn are the
      // endpoints of the axis line.  base and up determine which way the
      // tick marks are drawn along that line.  For 2-D, base and up are changed
      // later on so that the labels are right side up. DRM 16-APR-2001
      if (myAxis == X_AXIS) {
        if (side == PRIMARY) {
          base = new double[] {scale, 0.0, 0.0};
          up = new double[] {0.0, scale, scale};
          startp = new double[] {one * xmax,
                                 ymin - ((offset - 1.0) + line),
                                 zmin - ((offset - 1.0) + line)};
          startn = new double[] {one * xmin,
                                 ymin - ((offset - 1.0) + line),
                                 zmin - ((offset - 1.0) + line)};
          gridstartp = new double[] {one * xmax, ymin, zmin};
          gridstartn = new double[] {one * xmin, ymin, zmin};
        }
        else if (side == SECONDARY) {
          base = new double[] {-scale, 0.0, 0.0};
          up = new double[] {0.0, -scale, scale};
          startp = new double[] {one * xmax,
                                 ymax + ((offset - 1.0) + line),
                                 zmin - ((offset - 1.0) + line)};
          startn = new double[] {one * xmin,
                                 ymax + ((offset - 1.0) + line),
                                 zmin - ((offset - 1.0) + line)};
          gridstartp = new double[] {one * xmax, ymax, zmin};
          gridstartn = new double[] {one * xmin, ymax, zmin};
        }
        else if (side == TERTIARY) {
          base = new double[] {scale, 0.0, 0.0};
          up = new double[] {0.0, scale, -scale};
          startp = new double[] {one * xmax,
                                 ymin - ((offset - 1.0) + line),
                                 zmax + ((offset - 1.0) + line)};
          startn = new double[] {one * xmin,
                                 ymin - ((offset - 1.0) + line),
                                 zmax + ((offset - 1.0) + line)};
          gridstartp = new double[] {one * xmax, ymin, zmax};
          gridstartn = new double[] {one * xmin, ymin, zmax};
        }
        else { // side == QUATERNARY
          base = new double[] {-scale, 0.0, 0.0};
          up = new double[] {0.0, -scale, -scale};
          startp = new double[] {one * xmax,
                                 ymax + ((offset - 1.0) + line),
                                 zmax + ((offset - 1.0) + line)};
          startn = new double[] {one * xmin,
                                 ymax + ((offset - 1.0) + line),
                                 zmax + ((offset - 1.0) + line)};
          gridstartp = new double[] {one * xmax, ymax, zmax};
          gridstartn = new double[] {one * xmin, ymax, zmax};
        }
      }
      else if (myAxis == Y_AXIS) {
        if (side == PRIMARY) {
          base = new double[] {0.0, -scale, 0.0};
          up = new double[] {scale, 0.0, scale};
          startp = new double[] {xmin - ((offset - 1.0) + line),
                                 one * ymax,
                                 zmin - ((offset - 1.0) + line)};
          startn = new double[] {xmin - ((offset - 1.0) + line),
                                 one * ymin,
                                 zmin - ((offset - 1.0) + line)};
          gridstartp = new double[] {xmin, one * ymax, zmin};
          gridstartn = new double[] {xmin, one * ymin, zmin};
        }
        else if (side == SECONDARY) {
          base = new double[] {0.0, scale, 0.0};
          up = new double[] {-scale, 0.0, scale};
          startp = new double[] {xmax + ((offset - 1.0) + line),
                                 one * ymax,
                                 zmin - ((offset - 1.0) + line)};
          startn = new double[] {xmax + ((offset - 1.0) + line),
                                 one * ymin,
                                 zmin - ((offset - 1.0) + line)};
          gridstartp = new double[] {xmax, one * ymax, zmin};
          gridstartn = new double[] {xmax, one * ymin, zmin};
        }
        else if (side == TERTIARY) {
          base = new double[] {0.0, -scale, 0.0};
          up = new double[] {scale, 0.0, -scale};
          startp = new double[] {xmin - ((offset - 1.0) + line),
                                 one * ymax,
                                 zmax + ((offset - 1.0) + line)};
          startn = new double[] {xmin - ((offset - 1.0) + line),
                                 one * ymin,
                                 zmax + ((offset - 1.0) + line)};
          gridstartp = new double[] {xmin, one * ymax, zmax};
          gridstartn = new double[] {xmin, one * ymin, zmax};
        }
        else { // side == QUATERNARY
          base = new double[] {0.0, scale, 0.0};
          up = new double[] {-scale, 0.0, -scale};
          startp = new double[] {xmax + ((offset - 1.0) + line),
                                 one * ymax,
                                 zmax + ((offset - 1.0) + line)};
          startn = new double[] {xmax + ((offset - 1.0) + line),
                                 one * ymin,
                                 zmax + ((offset - 1.0) + line)};
          gridstartp = new double[] {xmax, one * ymax, zmax};
          gridstartn = new double[] {xmax, one * ymin, zmax};
        }
      }
      else if (myAxis == Z_AXIS) {
        if (side == PRIMARY) {
          base = new double[] {0.0, 0.0, -scale};
          up = new double[] {scale, scale, 0.0};
          startp = new double[] {xmin - ((offset - 1.0) + line),
                                 ymin - ((offset - 1.0) + line),
                                 one * zmax};
          startn = new double[] {xmin - ((offset - 1.0) + line),
                                 ymin - ((offset - 1.0) + line),
                                 one * zmin};
          gridstartp = new double[] {xmin, ymin, one * zmax};
          gridstartn = new double[] {xmin, ymin, one * zmin};
        }
        else if (side == SECONDARY) {
          base = new double[] {0.0, 0.0, scale};
          up = new double[] {-scale, scale, 0.0};
          startp = new double[] {xmax + ((offset - 1.0) + line),
                                 ymin - ((offset - 1.0) + line),
                                 one * zmax};
          startn = new double[] {xmax + ((offset - 1.0) + line),
                                 ymin - ((offset - 1.0) + line),
                                 one * zmin};
          gridstartp = new double[] {xmax, ymin, one * zmax};
          gridstartn = new double[] {xmax, ymin, one * zmin};
        }
        else if (side == TERTIARY) {
          base = new double[] {0.0, 0.0, -scale};
          up = new double[] {scale, -scale, 0.0};
          startp = new double[] {xmin - ((offset - 1.0) + line),
                                 ymax + ((offset - 1.0) + line),
                                 one * zmax};
          startn = new double[] {xmin - ((offset - 1.0) + line),
                                 ymax + ((offset - 1.0) + line),
                                 one * zmin};
          gridstartp = new double[] {xmin, ymax, one * zmax};
          gridstartn = new double[] {xmin, ymax, one * zmin};
        }
        else { // side == QUATERNARY
          base = new double[] {0.0, 0.0, scale};
          up = new double[] {-scale, -scale, 0.0};
          startp = new double[] {xmax + ((offset - 1.0) + line),
                                 ymax + ((offset - 1.0) + line),
                                 one * zmax};
          startn = new double[] {xmax + ((offset - 1.0) + line),
                                 ymax + ((offset - 1.0) + line),
                                 one * zmin};
          gridstartp = new double[] {xmax, ymax, one * zmax};
          gridstartn = new double[] {xmax, ymax, one * zmin};
        }
      }
  
      if (twoD) {
        if (myAxis == Z_AXIS) return false;  // can't have Z in 2D
        // zero out z coordinates
        base[2] = 0.0;
        up[2] = 0.0;
        startn[2] = 0.0;
        startp[2] = 0.0;
      }
      
      if (!labelRelief ) {
        up[2] = 0.0;        
      }
  
      // VisADLineArray coordinates have three entries for (x, y, z) of each point
      // two points determine a line segment,
      // hence 6 coordinates entries per segment
  
      // base line for axis
      if (baseLineVisible) // draw base line
      {
        VisADLineArray baseLineArray = new VisADLineArray();
        float[] lineCoordinates = new float[6];
        for (int i=0; i<3; i++) { // loop over x, y & z coordinates
          lineCoordinates[i] = (float) startn[i];
          lineCoordinates[3 + i] = (float) startp[i];
        }
        baseLineArray.vertexCount = 2;
        baseLineArray.coordinates = lineCoordinates;
        lineArrayVector.add(baseLineArray);
      }
  
      double range = Math.abs(dataRange[1] - dataRange[0]);
      double min = Math.min(dataRange[0], dataRange[1]);
      double max = Math.max(dataRange[0], dataRange[1]);
      //System.out.println(
      //  "range = " + range + " min = " + min + " max = " + max);
  
      // compute tick mark values
      double tens = 1.0;
      if (range < tens) {
        tens /= 10.0;
        while (range < tens) tens /= 10.0;
      }
      else {
        while (10.0 * tens <= range) tens *= 10.0;
      }
      // now tens <= range < 10.0 * tens;
      if (autoComputeTicks || majorTickSpacing <= 0)
      {
        double ratio = range / tens;
        if (ratio < 2.0) {
          tens = tens/5.0;
        }
        else if (ratio < 4.0) {
          tens = tens/2.0;
        }
        majorTickSpacing = tens;
      }
      // now tens = interval between major tick marks (majorTickSpacing)
      //System.out.println("computed ticks " + majorTickSpacing);

      //double[] hilo = computeTicks(max, min, tickBase, majorTickSpacing);
      double[] hilo;
      if (majorTicks == null) {
        hilo = computeTicks(max, min, tickBase, majorTickSpacing);
      } else {
    	hilo = computeTicks(max,min,majorTicks);
      }
  
      // firstValue is the first Tick mark value
      //double firstValue = hilo[0];
      //double botval = hilo[0];
      //double topval = hilo[hilo.length-1];
  
      // draw major tick marks
      VisADLineArray majorTickArray = new VisADLineArray();
      //int nticks = (int) ((topval-botval)/majorTickSpacing) + 1;
      int nticks = hilo.length;
      float[] majorCoordinates = new float[6 * nticks];
      double[] tickup = up;
      if (getTickOrientation() != PRIMARY)
      {
        if (myAxis == X_AXIS) {
          tickup = new double[] {up[0], -up[1], -up[2]};
        }
        else if (myAxis == Y_AXIS) {
          tickup = new double[] {-up[0], up[1], -up[2]};
        }
        else if (myAxis == Z_AXIS) {
          tickup = new double[] {-up[0], -up[1], up[2]};
        }
      }
      // initialize some stuff
      int k = 0;
      if (ticksVisible) {
        for (int j = 0; j< nticks; j++) //Change DRM 21-Feb-2001
        {
          //double value = firstValue + (j * majorTickSpacing);
          double value = hilo[j];
          double a = (value - min) / (max - min);
          for (int i=0; i<3; i++) {
            if ((k + 3 + i) < majorCoordinates.length) {
              // guard against error that cannot happen, but was seen?
              majorCoordinates[k + i] =
                (float) ((1.0 - a) * startn[i] + a * startp[i]);
              majorCoordinates[k + 3 + i] =
                (float) (majorCoordinates[k + i] - TICKSIZE * tickup[i]);
            }
          }
          k += 6;
        }
  
        majorTickArray.vertexCount = 2 * (nticks);
        majorTickArray.coordinates = majorCoordinates;
        lineArrayVector.add(majorTickArray);
      }
  
      if (gridLinesVisible && l == 0) {
        VisADLineArray gridArray = new VisADLineArray();
        float[] gridCoordinates = new float[6 * nticks];
        // initialize some stuff
        k = 0;
        double[] gridup = null;
        double gridLength = 1.0;
        if (myAxis == X_AXIS) {
          gridup = new double[] {0, up[1], 0};
          gridLength = (ymax-ymin)/scale;
        }
        else if (myAxis == Y_AXIS) {
          gridup = new double[] {up[0], 0, 0};
          gridLength = (xmax-xmin)/scale;
        }
        else if (myAxis == Z_AXIS) {
          gridup = new double[] {up[0], 0, 0};
          gridLength = (xmax-xmin)/scale;
        }
        for (int j = 0; j< nticks; j++) //Change DRM 21-Feb-2001
        {
          //double value = firstValue + (j * majorTickSpacing);
          double value = hilo[j];
          double a = (value - min) / (max - min);
          for (int i=0; i<3; i++) {
            if ((k + 3 + i) < gridCoordinates.length) {
              // guard against error that cannot happen, but was seen?
              gridCoordinates[k + i] =
                (float) ((1.0 - a) * gridstartn[i] + a * gridstartp[i]);
              gridCoordinates[k + 3 + i] =
                (float) (gridCoordinates[k + i] + gridLength*gridup[i]);
            }
          }
          k += 6;
        }
        gridArray.vertexCount = 2 * (nticks);
        gridArray.coordinates = gridCoordinates;
        lineArrayVector.add(gridArray);
      }
  
      // create an array for the minor ticks
      if ((getMinorTickSpacing() > 0 || minorTicks != null) && ticksVisible)  
      {
        //hilo = computeTicks(max, min, tickBase, minorTickSpacing);
        double[] minorTicksToDraw;
    	if (minorTicks == null) {
    	  minorTicksToDraw = computeTicks(max, min, tickBase, minorTickSpacing);
    	} else {
          minorTicksToDraw = computeTicks(max, min, minorTicks);
    	}
        // now lower * minorTickSpacing = value of lowest tick mark, and
        // upper * minorTickSpacing = values of highest tick mark
  
        VisADLineArray minorTickArray = new VisADLineArray();
        // Change DRM 21-Feb-2001
        //nticks = (int) ((hilo[hilo.length-1]-hilo[0])/minorTickSpacing) + 1;
        nticks = minorTicksToDraw.length;
        float[] minorCoordinates = new float[6 * nticks];
  
        // draw tick marks
        k = 0;
        //for (long j=lower; j<=upper; j++) {  // Change DRM 21-Feb-2001
        for (int j = 0; j < nticks; j++)
        {
          //double val = hilo[0] + (j * minorTickSpacing);
          double val = minorTicksToDraw[j];
          double a = (val - min) / (max - min);
          for (int i=0; i<3; i++) {
            if ((k + 3 + i) < minorCoordinates.length) {
              // guard against error that cannot happen, but was seen?
              minorCoordinates[k + i] =
                (float) ((1.0 - a) * startn[i] + a * startp[i]);
              // minor ticks are half the size of the major ticks
              minorCoordinates[k + 3 + i] =
                (float) (minorCoordinates[k + i] - TICKSIZE/2 * tickup[i]);
            }
          }
          k += 6;
        }
        minorTickArray.vertexCount = 2 * (nticks);
        minorTickArray.coordinates = minorCoordinates;
        lineArrayVector.add(minorTickArray);
      }
  
      // Title and labels
      // by default, all labels rendered centered
       TextControl.Justification justification =
         TextControl.Justification.CENTER;
  
      // PlotText is controlled by the initial starting point, base (controls
      // direction) and up (which way is up).  We handle 2D and 3D differently.
      // In 2-D, titles are drawn along the positive direction of the axis.
      // Labels are drawn in the Y-positive direction.
  
  
      // Labels first
      if (twoD) {
        if (myAxis == X_AXIS) {
           up = new double[] {0.0, scale, 0.0};
        }
        else if (myAxis == Y_AXIS) {
           up = new double[] {-scale, 0.0, 0.0};
        }
      }
  
      // Draw the labels.  If user hasn't defined their own, make defaults.
      if (!userLabels) {
        //createStandardLabels(topval, botval, botval, 
        //                     (labelAllTicks == false)
        //                        ?(topval - botval):majorTickSpacing, 
        //                     false);
    	createLabels(hilo, false);
      }
  
      double dist = 1.0 + TICKSIZE;   // dist from the line in the up direction;
      double[] updir = (twoD != true) ? up : new double[] {0.0, scale, 0.0};
      if (twoD) {
        base = new double[] {scale, 0.0, 0.0};
        if (myAxis == X_AXIS) {
           dist = (side == PRIMARY)
             ? (1.0 + TICKSIZE + .15)
             : -(TICKSIZE + .15);
        }
        else if (myAxis == Y_AXIS) {
           dist = (side == PRIMARY)
             ? -(TICKSIZE + .15)
             : (TICKSIZE + .15);
           justification =
             (side == PRIMARY)
                 ? TextControl.Justification.RIGHT
                 : TextControl.Justification.LEFT;
        }
      }
  
      // Added by Luke Catania on 05/07/2002
      // Added maximumYAxisTickLabelSize & yAxisLabelLength to calculate 
      // offset for Y-Axis label.
      //
      int maximumYAxisTickLabelSize = 1;
      int yAxisLabelLength=0;
      Hashtable localTable;
      synchronized(labelTable) {
          localTable = new Hashtable(labelTable);
      }
      for (Enumeration e = localTable.keys(); e.hasMoreElements();)
      {
        Double value;
        try {
          value = (Double) e.nextElement();
        } catch (ClassCastException cce) {
          throw new VisADException("Invalid keys in label hashtable");
        }
        double test = value.doubleValue();
        if (test > max || test < min) continue; // don't draw labels beyond range
  
        // Added by Luke Catania on 05/07/2002 - mods by DRM 28-Oct-2002
        // For Y-Axis only, calculate offset for axis label, so it does 
        // not overlap the tick labels.
        if (myAxis == Y_AXIS) {
          yAxisLabelLength = ((String) localTable.get(value)).length();
          if (yAxisLabelLength > maximumYAxisTickLabelSize)
            maximumYAxisTickLabelSize = yAxisLabelLength;
        }
        double val = (test - min) / (max - min);
        // center label on tick if Y axis and 2D
        if ((myAxis == Y_AXIS) && (twoD == true)) val -= .2 * scale; // HACK!!!!!
  
        double[] point = new double[3];
        for (int j=0; j < 3; j++) {
          point[j] = (1.0 - val) * startn[j] + val * startp[j] - dist * up[j];
  
  //        if (myAxis == Y_AXIS) System.out.println("Axis & Tick Label Position for " + test + ": " + startn[j] + ":" + startp[j] + ":" + point[j]);
        }
  
        /*
        System.out.println("For label = " + value.doubleValue() + "(" + val + "), point is (" + point[0] + "," + point[1] + "," + point[2] + ")");
        */
  
        if (labelFont == null)
        {
          VisADLineArray label =
            PlotText.render_label((String) localTable.get(value), point, base, updir, justification);
          lineArrayVector.add(label);
        }
        else if (labelFont instanceof Font)
        {
          VisADTriangleArray label =
            PlotText.render_font(
                (String) localTable.get(value), (Font) labelFont, point, base,
                updir, justification);
          labelArrayVector.add(label);
  
        } else if (labelFont instanceof HersheyFont) {
          VisADLineArray label =
            PlotText.render_font(
                (String) localTable.get(value), (HersheyFont) labelFont,
                   point, base, updir, justification);
          lineArrayVector.add(label);
        }
      }
  
      // Title
      double[] startlabel = new double[3];
      dist = 2.0 + TICKSIZE;   // dist from the line in the up direction;
      justification =
         TextControl.Justification.CENTER;
      if (twoD) {
        if (myAxis == X_AXIS) {
           base = new double[] {scale, 0.0, 0.0};
           up = new double[] {0.0, scale, 0.0};
           dist = (side == PRIMARY)
             ? 2.5 + TICKSIZE
             : -(1.5 + TICKSIZE - .05);
        }
        else if (myAxis == Y_AXIS) {
           base = new double[] {0.0, scale, 0.0};
           up = new double[] {-scale, 0.0, 0.0};
           dist = (side == PRIMARY)
             ? -(.5 + TICKSIZE + maximumYAxisTickLabelSize)
             : (.5 + TICKSIZE + maximumYAxisTickLabelSize) ;
        }
      }
      for (int i=0; i<3; i++) {
        startlabel[i] = 0.5 * (startn[i] + startp[i]) - dist * up[i];
      }
      /*
      System.out.println("For title, point is (" +
        startlabel[0] + "," + startlabel[1] + "," + startlabel[2] + ")");
      */
  
      if (labelFont == null)
      {
        VisADLineArray plotArray =
          PlotText.render_label(myTitle, startlabel, base, up, justification);
        lineArrayVector.add(plotArray);
      }
      else if (labelFont instanceof java.awt.Font)
      {
        VisADTriangleArray nameArray =
          PlotText.render_font(myTitle, (Font) labelFont,
                               startlabel, base, up, justification);
        labelArrayVector.add(nameArray);
      } else if (labelFont instanceof visad.util.HersheyFont) {
        VisADLineArray plotArray =
          PlotText.render_font(myTitle, (HersheyFont) labelFont,
                               startlabel, base, up, justification);
        lineArrayVector.add(plotArray);
      }
  
      // merge the line arrays
      VisADLineArray[] arrays =
          (VisADLineArray[]) lineArrayVector.toArray(
            new VisADLineArray[lineArrayVector.size()]);
      scaleArray = VisADLineArray.merge(arrays);
  
      // merge the label arrays
      labelArray = new VisADTriangleArray();
      if ( !(labelArrayVector.isEmpty()) )
      {
        VisADTriangleArray[] labelArrays =
            (VisADTriangleArray[]) labelArrayVector.toArray(
              new VisADTriangleArray[labelArrayVector.size()]);
        labelArray = VisADTriangleArray.merge(labelArrays);
        // set the color for the label arrays
        float[] rgb = myColor.getColorComponents(null);
        byte red = ShadowType.floatToByte(rgb[0]);
        byte green = ShadowType.floatToByte(rgb[1]);
        byte blue = ShadowType.floatToByte(rgb[2]);
        int n = 3 * labelArray.vertexCount;
        byte[] colors = new byte[n];
        for (int i=0; itrue value.
   * @param spacing  spacing between major tick marks (must be > 0)
   * @see #getMajorTickSpacing
   * @see #setAutoComputeTicks
   */
  public void setMajorTickSpacing(double spacing)
  {
    double oldValue = majorTickSpacing;
    majorTickSpacing = Math.abs(spacing);
    autoComputeTicks = false;
    if (majorTickSpacing != oldValue) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Set major tick marks.  Tick marks will be placed at the values on 
   * the axis.
   * @param majorTicks  the tick values
   */
  public void setMajorTicks(double[] majorTicks)
  {
    this.majorTicks = majorTicks;
    autoComputeTicks = false;
    try {
      scalarMap.makeScale();  // update the display
    }
    catch (VisADException ve) {;}
  }

  /**
   * Set minor tick marks.  Tick marks will be placed at the values on 
   * the axis.
   * @param minorTicks  the tick values
   */
  public void setMinorTicks(double[] minorTicks)
  {
    this.minorTicks = minorTicks;
    autoComputeTicks = false;
    try {
      scalarMap.makeScale();  // update the display
    }
    catch (VisADException ve) {;}
  }

  /**
   * This method returns the major tick spacing.  The number that is returned
   * represents the distance, measured in values, between each major tick mark.
   *
   * @return the number of values between major ticks
   * @see #setMajorTickSpacing
   */
  public double getMajorTickSpacing() {
    return majorTickSpacing;
  }

  /**
   * Set minor tick mark spacing. The number that is passed-in represents
   * the distance, measured in values, between each minor tick mark. If you
   * have a ScalarMap with a range from 0 to 50 and the minor tick spacing
   * is set to 10, you will get minor ticks next to the following values:
   * 0, 10, 20, 30, 40, 50.  This value will always be used unless
   * you call {@link #setAutoComputeTicks(boolean) setAutoComputeTicks}
   * with a true value.
   * @param spacing  spacing between minor tick marks (must be > 0)
   * @see #getMinorTickSpacing
   * @see #setAutoComputeTicks
   */
  public void setMinorTickSpacing(double spacing)
  {
    double oldValue = minorTickSpacing;
    minorTickSpacing = Math.abs(spacing);
    if (minorTickSpacing != oldValue) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * This method returns the minor tick spacing.  The number that is returned
   * represents the distance, measured in values, between each minor tick mark.
   *
   * @return the number of values between minor ticks
   * @see #setMinorTickSpacing
   */
  public double getMinorTickSpacing() {
    return minorTickSpacing;
  }

  /**
   * Allow the AxisScale to automatically compute the desired majorTickSpacing
   * based on the range of the ScalarMap.
   * @param b  if true, have majorTickSpacing automatically computed.
   */
  public void setAutoComputeTicks(boolean b)
  {
    boolean oldValue = autoComputeTicks;
    autoComputeTicks = b;
    if (autoComputeTicks != oldValue) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Creates a hashtable that will draw text labels starting at the
   * starting point specified using the increment field.
   * If you call createStandardLabels(100, 0, 2.0, 10.0), then it will
   * make labels for the values 2, 12, 22, 32, etc.
   *
   * @see #setLabelTable
   * @throws IllegalArgumentException  if min > max, or increment is
   *                                   greater than max-min
   */
  public void createStandardLabels(
    double max, double min, double base, double increment)
  {
    if (min > max) {
      throw new IllegalArgumentException("max must be greater than min");
    }
    if (increment > (max-min)) {
      throw new IllegalArgumentException(
        "increment must be less than or equal to range (max-min)");
    }
    createStandardLabels(max, min, base, increment, true);
  }

  /**
   * private copy to allow program to create table, but not remake scale
   */
  private void createStandardLabels(
    double max, double min, double base, double increment, boolean byuser)
  {
    synchronized(labelTable) {
       labelTable.clear();
       double[] values = computeTicks(max, min, base, increment);
       if (values != null) {
          for (int i = 0; i < values.length; i++) {
            labelTable.put(new Double(values[i]), createLabelString(values[i]));
          }
        }
    }
    if (byuser) {
      try {
        userLabels = true;
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * private copy to allow program to create table, but not remake scale
   */
  private void createLabels(double[] values, boolean byuser)
  {
    synchronized(labelTable) {
       labelTable.clear();
       if (values != null) {
    	 if (getLabelAllTicks()) {
            for (int i = 0; i < values.length; i++) {
              labelTable.put(new Double(values[i]), createLabelString(values[i]));
            }
         } else{
            labelTable.put(new Double(values[0]), createLabelString(values[0]));
            labelTable.put(new Double(values[values.length-1]), createLabelString(values[values.length-1]));
         }
       }
    }
    if (byuser) {
      try {
        userLabels = true;
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Used to specify what label will be drawn at any given value.
   * The key-value pairs are of this format:
   *     { Double value, java.lang.String}
   *
   * @param  labels  map of value/label pairs
   * @throws VisADException  invalid hashtable
   * @see #getLabelTable
   */
  public void setLabelTable( Hashtable labels )
    throws VisADException
  {
    Map oldTable = labelTable;
    labelTable = labels;
    if (labels != oldTable) {
      userLabels = true;
      scalarMap.makeScale();  // update the display
    }
  }

  /**
   * Get the Hashtable used for labels
   */
  public Hashtable getLabelTable()
  {
    return labelTable;
  }

  /**
   * Set the font used for rendering the labels
   * @param font  new font to use
   */
  public void setFont(Font font)
  {
    Object oldFont = labelFont;
    labelFont = font;
    if ((labelFont == null && oldFont != null) || 
    	(labelFont != null && !labelFont.equals(oldFont)))
    //if (labelFont != null && !labelFont.equals(oldFont))
    {
      if (labelFont != null && labelFont instanceof java.awt.Font) {
    	labelSize = ((Font) labelFont).getSize();
      }
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Set the font used for rendering the labels
   * @param font  new font to use
   */
  public void setFont(HersheyFont font)
  {
    Object oldFont = labelFont;
    labelFont = font;
    if ((labelFont == null && oldFont != null) || 
    	(labelFont != null && !labelFont.equals(oldFont)))
    //if (labelFont != null && !labelFont.equals(oldFont))
    {
      labelSize = 12;
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Get the font used for rendering the labels
   * @return  font use or null if using default text plot
   */
  public Font getFont()
  {
    return (labelFont instanceof Font) ? (Font)labelFont : null;
  }

  /**
   * Set visibility of base line.
   * @param  visible   true to display (default), false to turn off
   */
  public void setBaseLineVisible(boolean visible)
  {
    boolean oldValue = baseLineVisible;
    baseLineVisible = visible;
    if (baseLineVisible != oldValue) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Determine whether the base line for the scale should be visible
   * @return  true if line is visible, otherwise false;
   */
  public boolean getBaseLineVisible()
  {
    return baseLineVisible;
  }

  /**
   * Toggle whether the scale is along the box edge or not
   * @param b   true to snap to the box
   */
  public void setSnapToBox(boolean b)
  {
    boolean oldValue = snapToBox;
    snapToBox = b;
    if (snapToBox != oldValue) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Determine whether this property is set.
   * @return  true if property is set, otherwise false;
   */
  public boolean getSnapToBox()
  {
    return snapToBox;
  }

  /**
   * Sets the size of the labels.  You can use this to change the label
   * size when a Font is not being used.  If a Font
   * is being used and you call setLabelSize(), a new Font is
   * created using the old Font name and style, but with the
   * new size.
   * @param  size  font size to use
   * @see #setFont
   */
  public void setLabelSize(int size)
  {
    int oldSize = labelSize;
    labelSize = size;
    if (labelSize != oldSize) {
      if (labelFont != null) {
        if (labelFont instanceof java.awt.Font) labelFont =
            new Font( ((Font)labelFont).getName(),
                      ((Font)labelFont).getStyle(), labelSize);
      }
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Gets the size of the labels.
   * @return  relative size of labels
   */
  public int getLabelSize()
  {
    return labelSize;
  }

  /**
   * Sets the base value for tick marks.  This only applies when
   * setMajorTickSpacing or setMinorTickSpacing
   * have been called.
   * @param  base  base value for drawing tick marks.  For example, if
   *               your scale ranges from -4 to 18 and you set the
   *               major tick spacing to 5, you will get ticks at
   *               -4, 1, 6, 11, and 16 by default.  If you set the tick
   *               base value to 0, you will get ticks at 0, 5, 10, 15.
   */
  public void setTickBase(double base)
  {
    double oldBase = tickBase;
    tickBase = base;
    if (tickBase != oldBase) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Set side for axis (PRIMARY, SECONDARY)
   * @param side side for axis to appear on
   */
  public void setSide(int side)
  {
    // sanity check
    if (side != PRIMARY && side != SECONDARY &&
      side != TERTIARY && side != QUATERNARY)
    {
      return;
    }
    if (axisSide == side) return;
    axisSide = side;
    try {
      scalarMap.makeScale();  // update the display
    }
    catch (VisADException ve) {;}
  }

  /**
   * Get the alignment for the axis
   * @return  axis alignment (PRIMARY or SECONDARY)
   */
  public int getSide()
  {
    return axisSide;
  }

  /**
   * Set orientation of tick marks along the axis line.
   * @param orient (PRIMARY or SECONDARY)
   */
  public void setTickOrientation(int orient)
  {
    double oldOrient = tickOrient;
    tickOrient =
      (orient == SECONDARY) ? SECONDARY : PRIMARY;  // sanity check
    if (tickOrient != oldOrient) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Get the orientation for the ticks along the axis
   * @return  tick orientation (PRIMARY or SECONDARY)
   */
  public int getTickOrientation()
  {
    return tickOrient;
  }

  /**
   * Set the formatting for all labels
   * @param format  format string
   */
  public void setNumberFormat(NumberFormat format)
  {
    labelFormat = format;
  }

  /**
   * Get the formatting for labels.  May be null (if not set)
   * @return format used for labeling
   */
  public NumberFormat getNumberFormat() { return labelFormat; }

  /**
   * Set the visibility of the AxisScale
   * @param visible  true to display the AxisScale
   */
  public void setVisible(boolean visible) {
    boolean oldVisibility = visibility;
    visibility = visible;
    if (!(oldVisibility == visibility) ) {
      try {
        // check for case if this was called from scalarmap.setScaleEnable()
        if ( !(visible == scalarMap.getScaleEnable()) ) {
          scalarMap.setScaleEnable(visible);
	}
        scalarMap.makeScale();  // update the display
      } catch (VisADException ve) {;}
    }
  }

  /**
   * Get the visibility of the AxisScale
   * @return true if AxisScale is being rendered
   */
  public boolean isVisible() {
    return scalarMap.getScaleEnable();
  }

  /**
   * Set the visibility of the grid lines; Grid lines are placed
   * at major tick marks.
   * @param show  true to display the grid lines
   */
  public void setGridLinesVisible(boolean show) {
    boolean oldShow = gridLinesVisible;
    gridLinesVisible = show;
    if (!(oldShow == show) ) {
      try {
        scalarMap.makeScale();  // update the display
      } catch (VisADException ve) {;}
    }
  }

  /**
   * Get the visibility of the grid lines
   * @return true if grid lines are being rendered
   */
  public boolean getGridLinesVisible() {
    return gridLinesVisible;
  }

  /**
   * Set whether both sides are labeled.
   * @param both  true to label both sides
   */
  public void setLabelBothSides(boolean both) {
    boolean oldBoth = labelBothSides;
    labelBothSides = both;
    if (!(oldBoth == both) ) {
      try {
        scalarMap.makeScale();  // update the display
      } catch (VisADException ve) {;}
    }
  }

  /**
   * See if both sides are labeled
   * @return true if labelling is on both sides
   */
  public boolean getLabelBothSides() {
    return labelBothSides;
  }


  /**
   * Set whether ticks are visible
   * @param visible  true to show ticks
   */
  public void setTicksVisible(boolean visible) {
    boolean oldValue = ticksVisible;
    ticksVisible = visible;
    if (!(oldValue == visible) ) {
      try {
        scalarMap.makeScale();  // update the display
      } catch (VisADException ve) {;}
    }
  }

  /**
   * See if ticks are visible
   * @return true if labeling is on both sides
   */
  public boolean getTicksVisible() {
    return ticksVisible;
  }


  /**
   * Set whether all major ticks should be labeled.  The default is
   * to only label the first and last major tick.  This setting is 
   * ignored if user labels are being used or if user manually
   * calls {@link #createStandardLabels(double, double, double, double) 
   * createStandardLabels} or {@link #setLabelTable(Hashtable) 
   * setLabelTable}
   * @see #getLabelAllTicks()
   * @see #createStandardLabels(double, double, double, double)
   * @see #setLabelTable(Hashtable)
   *
   * @param labelAll  true to label all (major) ticks.  Overridden
   */
  public void setLabelAllTicks(boolean labelAll) {
    boolean oldValue = labelAllTicks;
    labelAllTicks = labelAll;
    if (labelAllTicks != oldValue) {
      try {
        scalarMap.makeScale();  // update the display
      }
      catch (VisADException ve) {;}
    }
  }

  /**
   * Return whether all major ticks are to be labeled.
   * @return true if ticks are to be labeled.
   */
  public boolean getLabelAllTicks() {
     return labelAllTicks;
  }

  /** compute the tick mark values */
  private double[] computeTicks(double high, double low,
                                double base, double interval)
  {
    double[] vals = null;

    // compute nlo and nhi, for low and high contour values in the box
    long nlo = Math.round((Math.ceil((low - base) / Math.abs(interval))));
    long nhi = Math.round((Math.floor((high - base) / Math.abs(interval))));

    // how many contour lines are needed.
    int numc = (int) (nhi - nlo) + 1;
    if (numc < 1) return new double[] {low, high};

    vals = new double[numc];

    for(int i = 0; i < numc; i++) {
      vals[i] = base + (nlo + i) * interval;
    }

    return vals;
  }

  /** compute the tick mark values that are between max and min */
  private double[] computeTicks(double max, double min,
                                double[] ticks)
  {
    double[] vals = new double[ticks.length];
    Arrays.sort(ticks);
    int numTicks = 0;
    for (int i = 0; i < ticks.length; i++) {
      double tick = ticks[i];
      if (tick <= max && tick >= min) { 
        vals[numTicks++] = tick;
      }
    }
    if (numTicks < ticks.length) {
       double[] newVals = new double[numTicks];
       System.arraycopy(vals,0,newVals,0,numTicks);
       return newVals;
    }
    return vals;
  }

  /** create the default string for a value */
  private String createLabelString(double value)
  {
    String     label = null;
    ScalarType sType = scalarMap.getScalar();
    if (sType instanceof RealType)
    {
      RealType rType = (RealType)sType;
      Unit     unit = rType.getDefaultUnit();
      if (Unit.canConvert(CommonUnit.secondsSinceTheEpoch, unit) &&
        !unit.getAbsoluteUnit().equals(unit))
      {
         label = new Real(rType, value).toValueString();
      }
      else
      {
        label =
          (labelFormat != null)
            ? labelFormat.format(value)
            : PlotText.shortString(value);
      }
    }
    else
    {
      label =
        (labelFormat != null)
          ? labelFormat.format(value)
          : PlotText.shortString(value);
    }
    return label;
  }

  /**
   * Checks if is label has relief.
   *
   * @return true, if is label has relief
   */
  public boolean isLabelRelief() {
    return labelRelief;
  }

  /**
   * Sets the label relief.
   *
   * @param labelRelief the new label relief
   */
  public void setLabelRelief(boolean labelRelief) {
    this.labelRelief = labelRelief;
  }

}

 

Ads help maintain this website.