/*
 * Copyright (c) 2002-2006 Samit Basu
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include "HandleAxis.hpp"
#include "HandleList.hpp"
#include "HandleFigure.hpp"
#include "HandleText.hpp"
#include "Core.hpp"
#include <qapplication.h>
#include <math.h>
#include <qpainter.h>
#include "QTRenderEngine.hpp"
#include "HandleCommands.hpp"
#include <qgl.h>
#include <QList>

QList<double> GetTicksOuter(double amin, double amax, bool isLog, int requestedCounts) {
  double arange = amax - amin;
  double astep = pow(10.0,floor(log10(arange)));
  double nsteps = arange/astep;
  double aval;
  QList<double> retvec;
  if (requestedCounts >= 10) {
    if (nsteps <= 1)
      astep /= 10.0;
    else if (nsteps <= 2)
      astep /= 5.0;
    else if (nsteps <= 5)
      astep /= 2.0;
  } else if (requestedCounts >= 5) {
    if (nsteps > 5) {
      astep *= 2.0;
      nsteps = arange/astep;
    }
    if (nsteps <= 1)
      astep /= 5.0;
  } else if (requestedCounts >= 2) {
    if (nsteps > 2) {
      astep *= 5.0;
      nsteps = arange/astep;
    }
    if (nsteps <= 1)
      astep /= 2.0;
  }
  if (isLog)
    astep = ceil(astep);
  if ((amin < 0) && (amax > 0)) {
    aval = 0;
    while (aval < amax) {
      retvec.push_back(aval);
      aval += astep;
    }
    retvec.push_back(aval);
    aval = -astep;
    while (aval > amin) {
      retvec.push_front(aval);
      aval -= astep;
    }
    retvec.push_front(aval);
  } else if ((amin == 0) && (amax > 0)) {
    aval = 0;
    while (aval < amax) {
      retvec.push_back(aval);
      aval += astep;
    }
    retvec.push_back(aval);
  } else if ((amin < 0) && (amax == 0)) {
    aval = 0;
    while (aval > amin) {
      retvec.push_front(aval);
      aval -= astep;
    }
    retvec.push_front(aval);
  } else {
    aval = floor(amin/astep)*astep;
    while (aval < amax) {
      retvec.push_back(aval);
      aval += astep;
    }
    retvec.push_back(aval);
  }
  return retvec;
}

QList<double> GetTicksInner(double amin, double amax, bool isLog, int requestedCounts) {
  double arange = amax - amin;
  double astep = pow(10.0,floor(log10(arange)));
  double nsteps = arange/astep;
  double aval;
  QList<double> retvec;
  if (requestedCounts >= 10) {
    if (nsteps <= 1)
      astep /= 10.0;
    else if (nsteps <= 2)
      astep /= 5.0;
    else if (nsteps <= 5)
      astep /= 2.0;
  } else if (requestedCounts >= 5) {
    if (nsteps > 5) {
      astep *= 2.0;
      nsteps = arange/astep;
    }
    if (nsteps <= 1)
      astep /= 5.0;
  } else if (requestedCounts >= 2) {
    if (nsteps > 2) {
      astep *= 5.0;
      nsteps = arange/astep;
    }
    if (nsteps <= 1)
      astep /= 2.0;
  }
  if (isLog) astep = ceil(astep);
  if ((amin < 0) && (amax > 0)) {
    aval = 0;
    while (aval <= amax) {
      retvec.push_back(aval);
      aval += astep;
    }
    aval = -astep;
    while (aval >= amin) {
      retvec.push_front(aval);
      aval -= astep;
    }
  } else if ((amin == 0) && (amax > 0)) {
    aval = 0;
    while (aval <= amax) {
      retvec.push_back(aval);
      aval += astep;
    }
  } else if ((amin < 0) && (amax == 0)) {
    aval = 0;
    while (aval >= amin) {
      retvec.push_front(aval);
      aval -= astep;
    }
  } else {
    aval = ceil(amin/astep)*astep;
    while (aval <= amax) {
      retvec.push_back(aval);
      aval += astep;
    }
  }
  return retvec;
}


#ifdef __FreeBSD__
#define log2(x) log(x)/log(2)
#endif

// Property list & status
//    activepositionproperty
//    alim
//    alimmode
//    ambientlightcolor
//    box
//    cameraposition - done
//    camerapositionmode  - done
//    cameratarget - done
//    cameratargetmode - done
//    cameraupvector - done
//    cameraviewangle 
//    cameraviewanglemode
//    children
//    clim
//    climmode
//    clipping
//    color - done (does 'none' work?)
//    colororder - done
//    dataaspectratio
//    dataaspectratiomode
//    fontangle - done
//    fontname - done
//    fontsize - done
//    fontunits - not implemented
//    fontweight - done
//    gridlinestyle - done
//    handlevisibility
//    hittest
//    interruptible
//    layer
//    //    linestyleorder
//    linewidth
//    minorgridlinestyle - done
//    nextplot
//    outerposition - done - need linkage to position
//    parent - done
//    plotboxaspectratio
//    plotboxaspectratiomode
//    position - done
//    projection
//    selected
//    selectionhighlight
//    tag - done
//    tickdir - done - need labels to not follow tick direction
//    tickdirmode - done
//    ticklength - need 2d support
//    tightinset
//    title
//    type
//    units
//    //    userdata
//    visible
//    xaxislocation - done
//    yaxislocation - done
//    xcolor - done
//    ycolor - done
//    zcolor - done
//    xdir - done
//    ydir - done
//    zdir - done
//    xgrid - done
//    ygrid - done
//    zgrid - done
//    xlabel - done
//    ylabel - done
//    zlabel - done
//    xlim - done
//    ylim - done
//    zlim - done
//    xlimmode - done
//    ylimmode - done
//    zlimmode - done
//    xminorgrid - done
//    yminorgrid - done
//    zminorgrid - done
//    xscale - done
//    yscale - done
//    zscale - done
//    xtick - done
//    ytick - done
//    ztick - done
//    xticklabel - done
//    yticklabel - done
//    zticklabel - done
//    xtickmode - done
//    ytickmode - done
//    ztickmode - done
//    xticklabelmode - done
//    yticklabelmode - done
//    zticklabelmode - done
 

// Need to build the transformation matrix...
// Given a position rectangle and a camera matrix,
//   1.  Map the 8 corners of the data space into the camera plane
//   1b. Use the data scaling factors in DataAspectRatio when
//       mapping the coordinates.
//   2.  Calculate scale factor & offsets appropriately to 
//       stretch the data to fit the position rectangle
//   Create a special 4x4 matrix that is the composite of 
//   the data scale, camera and xy scale matrices
//  
//   This is the final (view) matrix.
//
// Check out gluLookAt
//
// Actually, not...  it is more complicated than this.
// The view matrix maps _rotated_ x,y,z coordinates
// to the screen.  We want to specify the clipping
// volume in _unrotated_ coordinates.  So, what needs
// to happen is that we must transform the 8 corners
// of the clipped volume using the current modelview
// matrix, use them to establish new clipping planes
// in x, y, z, and then map the clipped volume to viewer
// space.

// Probably a better way to do this...

class pt3d {
public:
  double x;
  double y;
  double z;
  pt3d() : x(0), y(0), z(0) {}
  pt3d(double a, double b, double c) : x(a), y(b), z(c) {}
  pt3d operator-(const pt3d& t) {return pt3d(x-t.x,y-t.y,z-t.z);}
};

static pt3d crossprod(pt3d v1, pt3d v2) {
  pt3d temp;
  temp.x = (v1.y * v2.z) - (v1.z * v2.y);
  temp.y = (v1.z * v2.x) - (v1.x * v2.z);
  temp.z = (v1.x * v2.y) - (v1.y * v2.x);
  return temp;
}

static std::string TrimPrint(double val, bool scientificNotation) {
  char buffer[1000];
  char *p;
  if (!scientificNotation) {
    sprintf(buffer,"%f",val);
    p = buffer + strlen(buffer) - 1;
    while (*p == '0') {
      *p = 0;
      p--;
    }
    if ((*p == '.') || (*p == ',')) {
      *p = 0;
      //	*(p+1) = '0';
      //	*(p+2) = 0;
    }
    return std::string(buffer);
  } else {
    sprintf(buffer,"%e",val);
    std::string label(buffer);
    unsigned int ePtr;
    ePtr = label.size() - 1;
    while ((label[ePtr] != 'e') && (label[ePtr] != 'E'))
      ePtr--;
    ePtr--;
    while (label[ePtr] == '0') {
      label.erase(ePtr,1);
      ePtr--;
    }
    if ((label[ePtr] == '.') || (label[ePtr] == ','))
      label.insert(ePtr+1, 1,'0');
    ePtr = label.size() - 1;
    while ((label[ePtr] != 'e') && (label[ePtr] != 'E'))
      ePtr--;
    ePtr+=2;
    while ((label[ePtr] == '0') && ePtr < label.size()) {
      label.erase(ePtr,1);
    }
    if (ePtr == label.size())
      label.append("0");
    return label;
  }
}

const int MAX_TICK_COUNT = 1000;

double tlog(double x) {
  if (x>0) 
    return log10(x);
  else
    return -10;
}

// Construct an axis 
void FormatAxisManual(double t1, double t2, int tickcount,
		      bool isLogarithmic,
		      double& tStart, double &tStop,
		      std::vector<double> &tickLocations,
		      std::vector<std::string> &tlabels) {
  int tCount;
  tickLocations.clear();
  tlabels.clear();
  bool exponentialForm;
  exponentialForm = false;
  QList<double> tick_locations(GetTicksInner(t1,t2,isLogarithmic,tickcount));
  if (tick_locations.empty()) return;
  tStart = tick_locations.front();
  tStop = tick_locations.back();
  tCount = tick_locations.size();
  for (int i=0;i<tCount;i++) {
    double tloc = tick_locations[i];
    if (!isLogarithmic)
      tickLocations.push_back(tloc);
    else
      tickLocations.push_back(pow(10.0,tloc));
    if (tloc != 0.0)
      exponentialForm |= (fabs(log10(fabs(tloc))) >= 4.0);
  }
  for (int i=0;i<tCount;i++) {
    double tloc = tick_locations[i];
    if (!isLogarithmic)
      tlabels.push_back(TrimPrint(tloc,exponentialForm));
    else
      tlabels.push_back(TrimPrint(pow(10.0,tloc),true));
  }
}
  
void FormatAxisAuto(double tMin, double tMax, int tickcount,
		    bool isLogarithmic,
		    double& tStart, double &tStop,
		    std::vector<double> &tickLocations,
		    std::vector<std::string> &tlabels) {
  int tCount;
  tickLocations.clear();
  tlabels.clear();
  bool exponentialForm;
  exponentialForm = false;
  //  const double *dp = (const double *) trange.getDataPointer();
  QList<double> tick_locations(GetTicksOuter(tMin,tMax,isLogarithmic,tickcount));
  if (tick_locations.empty()) return;
  tStart = tick_locations.front();
  tStop = tick_locations.back();
  tCount = tick_locations.size();
  for (int i=0;i<tCount;i++) {
    double tloc = tick_locations[i];
    if (!isLogarithmic)
      tickLocations.push_back(tloc);
    else
      tickLocations.push_back(pow(10.0,tloc));
    if (tloc != 0.0)
      exponentialForm |= (fabs(log10(fabs(tloc))) >= 4.0);
  }
  for (int i=0;i<tCount;i++) {
    double tloc = tick_locations[i];
    if (!isLogarithmic)
      tlabels.push_back(TrimPrint(tloc,exponentialForm));
    else
      tlabels.push_back(TrimPrint(pow(10.0,tloc),true));
  }
}

void HandleAxis::GetMaxTickMetric(RenderEngine &gc,
				  std::vector<std::string> labs,
				  double &maxx, double &maxy) {
  maxx = 0;
  maxy = 0;
  for (int i=0;i<labs.size();i++) {
    int width, height, xoffset, yoffset;
    gc.measureText(labs[i],m_font,
		   RenderEngine::Min,RenderEngine::Min,
		   width, height, xoffset, yoffset);
    maxx = qMax(maxx,(double)width);
    maxy = qMax(maxy,(double)height);
  }      
}
  
void HandleAxis::ConstructProperties() {
  // These are all the properties of the axis
  //!
  //@Module AXISPROPERTIES Axis Object Properties
  //@@Section HANDLE
  //@@Usage
  //Below is a summary of the properties for the axis. 
  //\begin{itemize}
  //  \item @|activepositionproperty| - @|four vector| - Not used.
  //  \item @|alim| - @|two vector| - Controls the mapping of 
  // transparency.  The vector @|[a_1,a_2]|@ defines the scale for transparency.
  // Plots then map @|a_1| to a completely opaque value, and @|a_2| to a 
  // completely transparent value.  This mapping is applied to the alpha
  // data of the plot data.
  //  \item @|alimmode| - @|{'auto','manual'}| - For @|auto| mode, we 
  // map the alpha ranges of all objects in the plot to a full scale.
  // For @|manual| mode, we use the @|alim| vector.
  //  \item @|ambientlightcolor| - @|colorspec| - Not used.
  //  \item @|box| - @|On/Off| - Not used.
  //  \item @|cameraposition| - @|three vector| - Set the position for the
  // camera in axis space.
  //  \item @|camerapositionmode| - @|{'auto','manual'}| - For @|manual|
  // mode, the camera position is picked up from the @|cameraposition| vector.
  // For @|auto| mode, the camera position is set to be centered on the 
  // @|x| and @|y| axis limits, and beyond the @|z| maximum limit.
  //  \item @|cameratarget| - @|three vector| - Defines the point in axis
  // space that the camera is targetted at.
  //  \item @|cameratargetmode| - @|{'auto','manual'}| - For @|manual|
  // mode the camera target is picked up from the @|cameratarget| vector.  For
  // @|auto| mode, the camera target is chosen to be the center of the 
  // three axes.
  //  \item @|cameraupvector| - @|three vector| - Defines the upwards vector
  // for the camera (what is ultimately mapped to the vertical axis of the
  // plot or screen).  This vector must not be parallel to the vector that
  // is defined by the optical axis (i.e., the one connecting the target to the
  // camera position).
  //  \item @|cameraupvectormode| - @|{'auto','manual'}| - For @|manual|
  // mode, the camera up vector is picked up from the @|cameraupvector|.  The
  // @|auto| mode chooses the up vector to point along the positive @|y| axis.
  //  \item @|cameraviewangle| - @|scalar| - Not used.
  //  \item @|cameraviewanglemode| - @|{'auto','manual'}| - Not used.
  //  \item @|children| - @|vector of handles| - A vector containing handles to
  // children of the current axis.  Be careful as to how you manipulate this
  // vector.  FreeMat uses a reference counting mechanism for graphics objects,
  // so if you remove a handle from the @|children| property of an axis, and
  // you have not added it to the @|children| property of another object, it
  // will be deleted.
  //  \item @|clim| - @|two vector| - The color range vector.  This vector
  // contains two values that dictate how children of this axis get mapped
  // to the colormap.  Values between the two endpoints of this vector are mapped
  // to the extremes of the colormap.
  //  \item @|climmode| - @|{'auto','manual'}| - For @|auto| mode, the color limits
  // are chosen to span the colordata for all of the children objects.  For @|manual|
  // mode, the color mapping is based on @|clim|.
  //  \item @|clipping| - @|{'on','off'}| - Not used.
  //  \item @|color| - @|colorspec| - The color used to draw the background box
  // for the axes.  Defaults to white.
  //  \item @|colororder| - @|color vector| - A vector of color specs (in 
  // RGB) that are cycled between when drawing line plots into this axis.
  // The default is order blue,green,red,cyan,magenta,yellow,black.
  //  \item @|datalimits| - @|six vector| - A vector that contains the x, y and z
  // limits of the data for children of the current axis.  Changes to this
  // property are ignored - it is calculated by FreeMat based on the datasets.
  //  \item @|dataaspectratio| - @|three vector| - A vector that describes the
  // aspect ratio of the data.  You can think of this as the relative scaling of 
  // units for each axis.  For example, if one unit along the x axis is twice
  // as long as one unit along the y axis, you would specify a data aspect 
  // ratio of @|[2,1,1]|.
  //  \item @|dataaspectratiomode| - @|{'auto','manual'}| - When the data aspect 
  // ratio is set to @|manual|, the data is scaled by the data aspect ratio before
  // being plotted.  When the data aspect ratio mode is @|auto| a complex set of
  // rules are applied to determine how the data should be scaled.  If @|dataaspectratio|
  // mode is @|auto| and @|plotboxaspectratio| is @|auto|, then the default data aspect
  // ratio is set to @|[1,1,1]| and the default plot box aspect ratio is chosen proportional
  // to @|[xrange,yrange,zrange]|, where @|xrange| is the span of data along the @|x|
  // axis, and similarly for @|yrange| and @|zrange|.  If @|plotboxaspectratio| is set to
  // @|[px,py,pz]|, then the @|dataaspectratio| is set to @|[xrange/px,yrange/py,zrange/pz]|.
  // If one of the axes has been specified manually, then the data will be scaled to fit
  // the axes as well as possible.
  //  \item @|fontangle| - @|{'normal','italic','oblique'}| - The angle of the fonts used
  // for text labels (e.g., tick labels).
  //  \item @|fontsize| - @|scalar| - The size of fonts used for text labels (tick labels).
  //  \item @|fontunits| - Not used.
  //  \item @|fontweight| - @|{'normal','bold','light','demi'}| - The weight of the font used
  // for tick labels.
  //  \item @|gridlinestyle| - @|{'-','--',':','-.','none'}| - The line style to use for 
  // drawing the grid lines.  Defaults to @|':'|.
  //  \item @|handlevisibility| - Not used.
  //  \item @|hittest| - Not used.
  //  \item @|interruptible| - Not used.
  //  \item @|layer| - Not used.
  //  \item @|linestyleorder| - @|linestyle vector| - A vector of linestyles that are cycled
  // through when plotted line series.
  //  \item @|linewidth| - @|scalar| - The width of line used to draw grid lines, axis lines, 
  // and other lines.
  //  \item @|minorgridlinestyle| - @|{'-','--',':','-.','none'}| - The line style used for
  // drawing grid lines through minor ticks.
  //  \item @|nextplot| - @|{'add','replace','replacechildren'}| - Controls how the next plot
  // interacts with the axis.  If it is set to @|'add'| the next plot will be added to the
  // current axis.  If it is set to @|'replace'| the new plot replaces all of the previous
  // children.
  //  \item @|outerposition| - @|four vector| - Specifies the coordinates of the outermost
  // box that contains the axis relative to the containing figure.  This vector is in normalized
  // coordinates and corresponds to the @|x, y, width, height| coordinates of the box.
  //  \item @|parent| - @|handle| - The handle for the containing object (a figure).
  //  \item @|plotboxaspectratio| - @|three vector| - Controls the aspect ratio of the plot
  // box.  See the entry under @|dataaspectratio| for details on how FreeMat uses this
  // vector in combination with the axis limits and the @|plotboxaspectratio| to determine
  // how to scale the data. 
  //  \item @|plotboxaspectratiomode| - @|{'auto','manual'}| - The plot box aspect ratio mode
  // interacts with the @|dataaspectratiomode| and the axis limits.
  //  \item @|position| - @|fourvector| - The normalized coordinates of the plot box space.
  // Should be inside the rectable defined by @|outerposition|.
  //  \item @|projection| - Not used.
  //  \item @|selected| - Not used.
  //  \item @|selectionhighlight| - Not used.
  //  \item @|tag| - A string that can be set to tag the axes with a name.
  //  \item @|textheight| - @|scalar| - This value is set by FreeMat to the height of the
  // current font in pixels.  
  //  \item @|tickdir| - @|{'in','out'}| - The direction of ticks.  Defaults to @|'in'| for 2D
  // plots, and @|'out'| for 3D plots if @|tickdirmode| is @|auto|.
  //  \item @|tickdirmode| - @|{'auto','manual'}| - When set to @|'auto'| the @|tickdir| 
  // defaults to @|'in'| for 2D plots, and @|'out'| for 3D plots.
  //  \item @|ticklength| - @|two vector| - The first element is the length of the tick in 
  // 2D plots, and the second is the length of the tick in the 3D plots.  The lengths are 
  // described as fractions of the longer dimension (width or height).
  //  \item @|tightinset| - Not used.
  //  \item @|title| - @|handle| - The handle of the label used to represent the title of
  // the plot.
  //  \item @|type| - @|string| - Takes the value of @|'axes'| for objects of the axes type.
  //  \item @|units| - Not used.
  //  \item @|userdata| - @|array| - An arbitrary array you can set to anything you want.
  //  \item @|visible| - @|{'on','off'}| - If set to @|'on'| the axes are drawn as normal.
  // If set to @|'off'|, only the children of the axes are drawn. The plot box, axis lines,
  // and tick labels are not drawn.
  //  \item @|xaxislocation| - @|{'top','bottom'}| - Controls placement of the x axis.
  //  \item @|yaxislocation| - @|{'left','right'}| - Controls placement of the y axis.
  //  \item @|xcolor| - @|colorspec| - The color of the x elements including the the x axis
  // line, ticks, grid lines and tick labels
  //  \item @|ycolor| - @|colorspec| - The color of the y elements including the the y axis
  // line, ticks, grid lines and tick labels.
  //  \item @|zcolor| - @|colorspec| - The color of the z elements including the the z axis
  // line, ticks, grid lines and tick labels.
  //  \item @|xdir| - @|{'normal','reverse'}| - For @|normal|, axes are drawn as you
  // would expect (e.g, in default 2D mode, the x axis has values increasing from left
  // to right.  For @|reverse|, the x axis has values increasing from right to left.
  //  \item @|ydir| - @|{'normal','reverse'}| - For @|normal|, axes are drawn as you
  // would expect (e.g, in default 2D mode, the y axis has values increasing from bottom
  // to top.  For @|reverse|, the y axis has values increasing from top to bottom.
  //  \item @|zdir| - @|{'normal','reverse'}| - For @|normal|, axes are drawn as you
  // would expect. In default 3D mode, the z axis has values increasing in some direction
  // (usually up).  For @|reverse| the z axis increases in the opposite direction.
  //  \item @|xgrid| - @|{'on','off'}| - Set to @|on| to draw grid lines from ticks on
  // the x axis.
  //  \item @|ygrid| - @|{'on','off'}| - Set to @|on| to draw grid lines from ticks on
  // the y axis.
  //  \item @|zgrid| - @|{'on','off'}| - Set to @|on| to draw grid lines from ticks on
  // the z axis.
  //  \item @|xlabel| - @|handle| - The handle of the text label attached to the x axis.
  // The position of that label and the rotation angle is computed automatically by
  // FreeMat.
  //  \item @|ylabel| - @|handle| - The handle of the text label attached to the y axis.
  // The position of that label and the rotation angle is computed automatically by
  // FreeMat.
  //  \item @|zlabel| - @|handle| - The handle of the text label attached to the z axis.
  // The position of that label and the rotation angle is computed automatically by
  // FreeMat.
  //  \item @|xlim| - @|two vector| - Contains the limits of the data along the x axis.
  // These are set automatically for @|xlimmode|.  When manually set it allows you to
  // zoom into the data.  The first element of this vector should be the smallest x value
  // you want mapped to the axis, and the second element should be the largest.
  //  \item @|ylim| - @|two vector| - Contains the limits of the data along the y axis.
  // These are set automatically for @|ylimmode|.  When manually set it allows you to
  // zoom into the data.  The first element of this vector should be the smallest y value
  // you want mapped to the axis, and the second element should be the largest.
  //  \item @|zlim| - @|two vector| - Contains the limits of the data along the z axis.
  // These are set automatically for @|zlimmode|.  When manually set it allows you to
  // zoom into the data.  The first element of this vector should be the smallest z value
  // you want mapped to the axis, and the second element should be the largest.
  //  \item @|xlimmode| - @|{'auto','manual'}| - Determines if @|xlim| is determined
  // automatically or if it is determined manually.  When determined automatically, it
  // is chosen to span the data range (at least).
  //  \item @|ylimmode| - @|{'auto','manual'}| - Determines if @|ylim| is determined
  // automatically or if it is determined manually.  When determined automatically, it
  // is chosen to span the data range (at least).
  //  \item @|zlimmode| - @|{'auto','manual'}| - Determines if @|zlim| is determined
  // automatically or if it is determined manually.  When determined automatically, it
  // is chosen to span the data range (at least).
  //  \item @|xminorgrid| - @|{'on','off'}| - Set to @|on| to draw grid lines from minor ticks on
  // the x axis.
  //  \item @|yminorgrid| - @|{'on','off'}| - Set to @|on| to draw grid lines from minor ticks on
  // the y axis.
  //  \item @|zminorgrid| - @|{'on','off'}| - Set to @|on| to draw grid lines from minor ticks on
  // the z axis.
  //  \item @|xscale| - @|{'linear','log'}| - Determines if the data on the x axis is linear or
  // logarithmically scaled.
  //  \item @|yscale| - @|{'linear','log'}| - Determines if the data on the y axis is linear or
  // logarithmically scaled.
  //  \item @|zscale| - @|{'linear','log'}| - Determines if the data on the z axis is linear or
  // logarithmically scaled.
  //  \item @|xtick| - @|vector| - A vector of x coordinates where ticks are placed on the 
  // x axis.  Setting this vector allows you complete control over the placement of ticks on 
  // the axis.
  //  \item @|ytick| - @|vector| - A vector of y coordinates where ticks are placed on the 
  // y axis.  Setting this vector allows you complete control over the placement of ticks on 
  // the axis.
  //  \item @|ztick| - @|vector| - A vector of z coordinates where ticks are placed on the 
  // z axis.  Setting this vector allows you complete control over the placement of ticks on 
  // the axis.
  //  \item @|xticklabel| - @|string vector| - A string vector, of the form @|'string|string|string'|
  // that contains labels to assign to the labels on the axis.  If this vector is shorter than
  // @|xtick|, then FreeMat will cycle through the elements of this vector to fill out the labels.
  //  \item @|yticklabel| - @|string vector| - A string vector, of the form @|'string|string|string'|
  // that contains labels to assign to the labels on the axis.  If this vector is shorter than
  // @|ytick|, then FreeMat will cycle through the elements of this vector to fill out the labels.
  //  \item @|zticklabel| - @|string vector| - A string vector, of the form @|'string|string|string'|
  // that contains labels to assign to the labels on the axis.  If this vector is shorter than
  // @|ztick|, then FreeMat will cycle through the elements of this vector to fill out the labels.
  //  \item @|xtickmode| - @|{'auto','manual'}| - Set to @|'auto'| if you want FreeMat to calculate
  // the tick locations.  Setting @|'xtick'| will cause this property to switch to @|'manual'|.
  //  \item @|ytickmode| - @|{'auto','manual'}| - Set to @|'auto'| if you want FreeMat to calculate
  // the tick locations.  Setting @|'ytick'| will cause this property to switch to @|'manual'|.
  //  \item @|ztickmode| - @|{'auto','manual'}| - Set to @|'auto'| if you want FreeMat to calculate
  // the tick locations.  Setting @|'ztick'| will cause this property to switch to @|'manual'|.
  //  \item @|xticklabelmode| - @|{'auto','manual'}| - Set to @|'auto'| if you want FreeMat to
  // set the tick labels.  This will be based on the vector @|xtick|.
  //  \item @|yticklabelmode| - @|{'auto','manual'}| - Set to @|'auto'| if you want FreeMat to
  // set the tick labels.  This will be based on the vector @|ytick|.
  //  \item @|zticklabelmode| - @|{'auto','manual'}| - Set to @|'auto'| if you want FreeMat to
  // set the tick labels.  This will be based on the vector @|ztick|.
  //\end{itemize}
  //!
  AddProperty(new HPPosition, "activepositionproperty");
  AddProperty(new HPTwoVector,"alim");
  AddProperty(new HPAutoManual,"alimmode");
  AddProperty(new HPColor,"ambientlightcolor");
  AddProperty(new HPOnOff,"box");
  AddProperty(new HPThreeVector,"cameraposition");
  AddProperty(new HPAutoManual,"camerapositionmode");
  AddProperty(new HPThreeVector,"cameratarget");
  AddProperty(new HPAutoManual,"cameratargetmode");
  AddProperty(new HPThreeVector,"cameraupvector");
  AddProperty(new HPAutoManual,"cameraupvectormode");
  AddProperty(new HPScalar,"cameraviewangle");
  AddProperty(new HPAutoManual,"cameraviewanglemode");
  AddProperty(new HPHandles,"children");
  AddProperty(new HPTwoVector,"clim");
  AddProperty(new HPAutoManual,"climmode");
  AddProperty(new HPOnOff,"clipping");
  AddProperty(new HPColor,"color");
  AddProperty(new HPColorVector,"colororder");
  AddProperty(new HPSixVector,"datalimits");
  AddProperty(new HPThreeVector,"dataaspectratio");
  AddProperty(new HPAutoManual,"dataaspectratiomode");
  AddProperty(new HPFontAngle,"fontangle");
  AddProperty(new HPString,"fontname");
  AddProperty(new HPScalar,"fontsize");
  AddProperty(new HPFontUnits,"fontunits");
  AddProperty(new HPFontWeight,"fontweight");
  AddProperty(new HPLineStyle,"gridlinestyle");
  AddProperty(new HPOnOff,"handlevisibility");
  AddProperty(new HPOnOff,"hittest");
  AddProperty(new HPOnOff,"interruptible");
  AddProperty(new HPTopBottom,"layer");
  AddProperty(new HPLineStyleOrder,"linestyleorder");
  AddProperty(new HPScalar,"linewidth");
  AddProperty(new HPLineStyle,"minorgridlinestyle");
  AddProperty(new HPNextPlotMode,"nextplot");
  AddProperty(new HPFourVector,"outerposition");
  AddProperty(new HPHandles,"parent");
  AddProperty(new HPThreeVector,"plotboxaspectratio");
  AddProperty(new HPAutoManual,"plotboxaspectratiomode");
  AddProperty(new HPFourVector,"position");
  AddProperty(new HPProjectionMode,"projection");
  AddProperty(new HPOnOff,"selected");
  AddProperty(new HPOnOff,"selectionhighlight");
  AddProperty(new HPString,"tag");
  AddProperty(new HPScalar,"textheight");
  AddProperty(new HPInOut,"tickdir");
  AddProperty(new HPAutoManual,"tickdirmode");
  AddProperty(new HPTwoVector,"ticklength");
  AddProperty(new HPFourVector,"tightinset");
  AddProperty(new HPHandles,"title");
  AddProperty(new HPString,"type");
  AddProperty(new HPUnits,"units");
  AddProperty(new HPArray,"userdata");
  AddProperty(new HPOnOff,"visible");
  AddProperty(new HPTopBottom,"xaxislocation");
  AddProperty(new HPLeftRight,"yaxislocation");
  AddProperty(new HPColor,"xcolor");
  AddProperty(new HPColor,"ycolor");
  AddProperty(new HPColor,"zcolor");
  AddProperty(new HPNormalReverse,"xdir");
  AddProperty(new HPNormalReverse,"ydir");
  AddProperty(new HPNormalReverse,"zdir");
  AddProperty(new HPOnOff,"xgrid");
  AddProperty(new HPOnOff,"ygrid");
  AddProperty(new HPOnOff,"zgrid");
  AddProperty(new HPHandles,"xlabel");
  AddProperty(new HPHandles,"ylabel");
  AddProperty(new HPHandles,"zlabel");
  AddProperty(new HPTwoVector,"xlim");
  AddProperty(new HPTwoVector,"ylim");
  AddProperty(new HPTwoVector,"zlim");
  AddProperty(new HPAutoManual,"xlimmode");
  AddProperty(new HPAutoManual,"ylimmode");
  AddProperty(new HPAutoManual,"zlimmode");
  AddProperty(new HPOnOff,"xminorgrid");
  AddProperty(new HPOnOff,"yminorgrid");
  AddProperty(new HPOnOff,"zminorgrid");
  AddProperty(new HPLinearLog,"xscale");
  AddProperty(new HPLinearLog,"yscale");
  AddProperty(new HPLinearLog,"zscale");
  AddProperty(new HPVector,"xtick");
  AddProperty(new HPVector,"ytick");
  AddProperty(new HPVector,"ztick");
  AddProperty(new HPStringSet,"xticklabel");
  AddProperty(new HPStringSet,"yticklabel");
  AddProperty(new HPStringSet,"zticklabel");
  AddProperty(new HPAutoManual,"xtickmode");
  AddProperty(new HPAutoManual,"ytickmode");
  AddProperty(new HPAutoManual,"ztickmode");
  AddProperty(new HPAutoManual,"xticklabelmode");
  AddProperty(new HPAutoManual,"yticklabelmode");
  AddProperty(new HPAutoManual,"zticklabelmode");
}

HandleAxis::HandleAxis() {
  ConstructProperties();
  SetupDefaults();
}

HandleAxis::~HandleAxis() {
}

void HandleAxis::SetupDefaults() {
  SetConstrainedStringDefault("activepositionproperty","outerposition");
  SetConstrainedStringDefault("alimmode","auto");
  SetConstrainedStringDefault("box","off");
  SetThreeVectorDefault("cameraposition",0,0,1);
  SetConstrainedStringDefault("camerapositionmode","auto");
  SetThreeVectorDefault("cameratarget",0,0,0);
  SetConstrainedStringDefault("cameratargetmode","auto");
  SetThreeVectorDefault("cameraupvector",0,1,0);
  SetConstrainedStringDefault("cameraupvectormode","auto");
  SetConstrainedStringDefault("cameraviewanglemode","auto");
  SetConstrainedStringDefault("climmode","auto");
  SetConstrainedStringDefault("clipping","on");
  SetThreeVectorDefault("color",1,1,1);
  // Set up the default color order
  std::vector<double> colors;
  colors.push_back(0.0); colors.push_back(0.0); colors.push_back(1.0); 
  colors.push_back(0.0); colors.push_back(0.5); colors.push_back(0.0); 
  colors.push_back(1.0); colors.push_back(0.0); colors.push_back(0.0);
  colors.push_back(0.0); colors.push_back(.75); colors.push_back(.75);
  colors.push_back(.75); colors.push_back(0.0); colors.push_back(.75);
  colors.push_back(.75); colors.push_back(.75); colors.push_back(0.0);
  colors.push_back(.25); colors.push_back(.25); colors.push_back(.25);
  HPVector *hp = (HPVector*) LookupProperty("colororder");
  hp->Data(colors);
  SetThreeVectorDefault("dataaspectratio",1,1,1);
  SetConstrainedStringDefault("dataaspectratiomode","auto");
  //    SetConstrainedStringDefault("drawmode","normal");
  SetConstrainedStringDefault("fontangle","normal");
  SetStringDefault("fontname","helvetica");
  SetScalarDefault("fontsize",10);
  SetConstrainedStringDefault("fontunits","points");
  SetConstrainedStringDefault("fontweight","normal");
  SetConstrainedStringDefault("gridlinestyle",":");
  SetConstrainedStringDefault("handlevisibility","on");
  SetConstrainedStringDefault("hittest","on");
  SetConstrainedStringDefault("interruptible","on");
  SetConstrainedStringDefault("layer","bottom");
  SetScalarDefault("linewidth",1.0);
  SetConstrainedStringSetDefault("linestyleorder","-|--|:|-.");
  SetConstrainedStringDefault("minorgridlinestyle",":");
  SetFourVectorDefault("outerposition",0,0,1,1);
  SetConstrainedStringDefault("nextplot","replace");
  SetThreeVectorDefault("plotboxaspectratio",1,1,1);
  SetConstrainedStringDefault("plotboxaspectratiomode","auto");
  SetFourVectorDefault("position",0.13,0.11,0.775,0.815);
  SetConstrainedStringDefault("projection","orthographic");
  SetConstrainedStringDefault("selected","off");
  SetConstrainedStringDefault("selectionhighlight","on");
  SetConstrainedStringDefault("tickdir","in");
  SetConstrainedStringDefault("tickdirmode","auto");
  SetTwoVectorDefault("ticklength",0.01,0.025);
  SetStringDefault("type","axes");
  SetConstrainedStringDefault("units","normalized");
  SetConstrainedStringDefault("visible","on");
  SetConstrainedStringDefault("xaxislocation","bottom");
  SetConstrainedStringDefault("yaxislocation","left");
  SetThreeVectorDefault("xcolor",0,0,0);
  SetThreeVectorDefault("ycolor",0,0,0);
  SetThreeVectorDefault("zcolor",0,0,0);
  SetConstrainedStringDefault("xdir","normal");
  SetConstrainedStringDefault("ydir","normal");
  SetConstrainedStringDefault("zdir","normal");
  SetConstrainedStringDefault("xgrid","off");
  SetConstrainedStringDefault("ygrid","off");
  SetConstrainedStringDefault("zgrid","off");
  SetTwoVectorDefault("xlim",0,1);
  SetTwoVectorDefault("ylim",0,1);
  SetTwoVectorDefault("zlim",0,1);
  SetConstrainedStringDefault("xlimmode","auto");
  SetConstrainedStringDefault("ylimmode","auto");
  SetConstrainedStringDefault("zlimmode","auto");
  SetConstrainedStringDefault("xminorgrid","off");
  SetConstrainedStringDefault("yminorgrid","off");
  SetConstrainedStringDefault("zminorgrid","off");
  SetConstrainedStringDefault("xscale","linear");
  SetConstrainedStringDefault("yscale","linear");
  SetConstrainedStringDefault("zscale","linear");
  SetConstrainedStringDefault("xtickmode","auto");
  SetConstrainedStringDefault("ytickmode","auto");
  SetConstrainedStringDefault("ztickmode","auto");
  SetConstrainedStringDefault("xticklabelmode","auto");
  SetConstrainedStringDefault("yticklabelmode","auto");
  SetConstrainedStringDefault("zticklabelmode","auto");
  UpdateAxisFont();
}

void HandleAxis::SetAxisLimits(std::vector<double> lims) {
  //     qDebug("Set Limits %f %f %f %f %f %f",
  // 	   lims[0],lims[1],lims[2],lims[3],lims[4],lims[5]);
  HPLinearLog *sp;
  sp = (HPLinearLog*) LookupProperty("xscale");
  if (sp->Is("linear")) 
    SetTwoVectorDefault("xlim",lims[0],lims[1]);
  else 
    SetTwoVectorDefault("xlim",pow(10.0,lims[0]),pow(10.0,lims[1]));
  sp = (HPLinearLog*) LookupProperty("yscale");
  if (sp->Is("linear")) 
    SetTwoVectorDefault("ylim",lims[2],lims[3]);
  else 
    SetTwoVectorDefault("ylim",pow(10.0,lims[2]),pow(10.0,lims[3]));
  sp = (HPLinearLog*) LookupProperty("zscale");
  if (sp->Is("linear")) 
    SetTwoVectorDefault("zlim",lims[4],lims[5]);
  else 
    SetTwoVectorDefault("zlim",pow(10.0,lims[4]),pow(10.0,lims[5]));
}

std::vector<double> HandleAxis::GetAxisLimits() {
  HPTwoVector *hp;
  std::vector<double> lims;
  HPLinearLog *sp;
  hp = (HPTwoVector*) LookupProperty("xlim");
  sp = (HPLinearLog*) LookupProperty("xscale");
  if (sp->Is("linear")) {
    lims.push_back(hp->Data()[0]);
    lims.push_back(hp->Data()[1]);
  } else {
    lims.push_back(tlog(hp->Data()[0]));
    lims.push_back(tlog(hp->Data()[1]));
  }
  hp = (HPTwoVector*) LookupProperty("ylim");
  sp = (HPLinearLog*) LookupProperty("yscale");
  if (sp->Is("linear")) {
    lims.push_back(hp->Data()[0]);
    lims.push_back(hp->Data()[1]);
  } else {
    lims.push_back(tlog(hp->Data()[0]));
    lims.push_back(tlog(hp->Data()[1]));
  }
  hp = (HPTwoVector*) LookupProperty("zlim");
  sp = (HPLinearLog*) LookupProperty("zscale");
  if (sp->Is("linear")) {
    lims.push_back(hp->Data()[0]);
    lims.push_back(hp->Data()[1]);
  } else {
    lims.push_back(tlog(hp->Data()[0]));
    lims.push_back(tlog(hp->Data()[1]));
  }
  //     qDebug("Get Limits %f %f %f %f %f %f",
  // 	   lims[0],lims[1],lims[2],lims[3],lims[4],lims[5]);    
  return lims;
}

HandleFigure* HandleAxis::GetParentFigure() {
  // Get our parent - should be a figure
  HPHandles *parent = (HPHandles*) LookupProperty("parent");
  if (parent->Data().empty()) return NULL;
  unsigned parent_handle = parent->Data()[0];
  HandleFigure *fig = LookupHandleFigure(parent_handle);
  return fig;
}

std::vector<double> HandleAxis::UnitsReinterpret(std::vector<double> a) {
  HandleFigure *fig = GetParentFigure();
  unsigned width = fig->GetWidth();
  unsigned height = fig->GetHeight();
  HPUnits *hp = (HPUnits*) LookupProperty("units");
  if (hp->Is("normalized")) {
    for (int i=0;i<a.size();i+=2) {
      a[i] *= width;
      a[i+1] *= height;
    }
    return a;
  } else if (hp->Is("pixels")) {
    return a;
  } else {
    throw Exception("Units of " + hp->Data() + " not yet implemented - please file a Request For Enhancement (RFE) on the FreeMat web site");
  }
}
  
std::vector<double> HandleAxis::GetPropertyVectorAsPixels(std::string name) {
  HPFourVector *hp = (HPFourVector*) LookupProperty(name);
  return (UnitsReinterpret(hp->Data()));
}

static void MinMaxVector(double *vals, int len, double &vmin, double &vmax) {
  vmin = vmax = vals[0];
  for (int i=0;i<len;i++) {
    vmin = (vals[i] < vmin) ? vals[i] : vmin;
    vmax = (vals[i] > vmax) ? vals[i] : vmax;
  }
}

std::vector<double> HandleAxis::ReMap(std::vector<double> t) {
  std::vector<double> s;
  for (int i=0;i<t.size();i+=3) {
    s.push_back(MapX(t[i]));
    s.push_back(MapY(t[i+1]));
    s.push_back(MapZ(t[i+2]));
  }
  return s;
}

void HandleAxis::ReMap(std::vector<double> xs, std::vector<double> ys,
		       std::vector<double> zs, std::vector<double> &ax,
		       std::vector<double> &ay, std::vector<double> &az) {
  for (int i=0;i<xs.size();i++) {
    ax.push_back(MapX(xs[i]));
    ay.push_back(MapY(ys[i]));
    az.push_back(MapZ(zs[i]));
  }    
}
  
// x in [a,b]
// a->b
// b->a
// y = a-x+b = (a+b) - x
double HandleAxis::MapX(double x) {
  HPNormalReverse *hp;    
  hp = (HPNormalReverse*) LookupProperty("xdir");
  HPTwoVector *xlim;
  xlim = (HPTwoVector*) LookupProperty("xlim");
  std::vector<double> lims(xlim->Data());
  HPLinearLog *sp;
  sp = (HPLinearLog*) LookupProperty("xscale");
  if (sp->Is("log"))
    x = tlog(x);
  double xmin(lims[0]);
  double xmax(lims[1]);
  if (hp->Is("reverse")) 
    return(xmin+xmax-x);
  else
    return(x);
}

double HandleAxis::MapY(double y) {
  HPNormalReverse *hp;    
  hp = (HPNormalReverse*) LookupProperty("ydir");
  HPTwoVector *ylim;
  ylim = (HPTwoVector*) LookupProperty("ylim");
  std::vector<double> lims(ylim->Data());
  HPLinearLog *sp;
  sp = (HPLinearLog*) LookupProperty("yscale");
  if (sp->Is("log"))
    y = tlog(y);
  double ymin(lims[0]);
  double ymax(lims[1]);
  if (hp->Is("reverse")) 
    return(ymin+ymax-y);
  else
    return(y);
}

double HandleAxis::MapZ(double z) {
  HPNormalReverse *hp;    
  hp = (HPNormalReverse*) LookupProperty("zdir");
  HPTwoVector *zlim;
  zlim = (HPTwoVector*) LookupProperty("zlim");
  std::vector<double> lims(zlim->Data());
  HPLinearLog *sp;
  sp = (HPLinearLog*) LookupProperty("zscale");
  if (sp->Is("log"))
    z = tlog(z);
  double zmin(lims[0]);
  double zmax(lims[1]);
  if (hp->Is("reverse")) 
    return(zmin+zmax-z);
  else
    return(z);
}

void rescale(double& amin, double &amax, double &ascale) {
  double amean = (amin+amax)/2.0;
  amin = amean-ascale*(amean-amin);
  amax = amean+ascale*(amax-amean);
}

void rerange(double& amin, double& amax, double arange) {
  double amean = (amin+amax)/2.0;
  amin = amean-arange/2.0;
  amax = amean+arange/2.0;
}

void HandleAxis::SetupProjection(RenderEngine &gc) {
  HPThreeVector *tv1, *tv2, *tv3;
  tv1 = (HPThreeVector*) LookupProperty("cameraposition");
  tv2 = (HPThreeVector*) LookupProperty("cameratarget");
  tv3 = (HPThreeVector*) LookupProperty("cameraupvector");
  gc.lookAt(tv1->Data()[0],tv1->Data()[1],tv1->Data()[2],
	    tv2->Data()[0],tv2->Data()[1],tv2->Data()[2],
	    tv3->Data()[0],tv3->Data()[1],tv3->Data()[2]);
  // Scale using the data aspect ratio
  std::vector<double> dar(VectorPropertyLookup("dataaspectratio"));
  gc.scale(1.0/dar[0],1.0/dar[1],1.0/dar[2]);
  // Get the axis limits
  std::vector<double> limits(GetAxisLimits());
  // Map the 8 corners of the clipping cube to rotated space
  double xvals[8];
  double yvals[8];
  double zvals[8];
  gc.mapPoint(limits[0],limits[2],limits[4],xvals[0],yvals[0],zvals[0]);
  gc.mapPoint(limits[0],limits[2],limits[5],xvals[1],yvals[1],zvals[1]);
  gc.mapPoint(limits[0],limits[3],limits[4],xvals[2],yvals[2],zvals[2]);
  gc.mapPoint(limits[0],limits[3],limits[5],xvals[3],yvals[3],zvals[3]);
  gc.mapPoint(limits[1],limits[2],limits[4],xvals[4],yvals[4],zvals[4]);
  gc.mapPoint(limits[1],limits[2],limits[5],xvals[5],yvals[5],zvals[5]);
  gc.mapPoint(limits[1],limits[3],limits[4],xvals[6],yvals[6],zvals[6]);
  gc.mapPoint(limits[1],limits[3],limits[5],xvals[7],yvals[7],zvals[7]);
  // Get the min and max x, y and z coordinates
  double xmin, xmax, ymin, ymax, zmin, zmax;
  MinMaxVector(xvals,8,xmin,xmax);
  MinMaxVector(yvals,8,ymin,ymax);
  MinMaxVector(zvals,8,zmin,zmax);
  double mzmin = qMin(fabs(zmin),fabs(zmax));
  double mzmax = qMax(fabs(zmin),fabs(zmax));
  if (zmin == zmax) {
    zmin = zmax-1;
    zmax = zmax+1;
  }
  std::vector<double> position(GetPropertyVectorAsPixels("position"));
  if (StringCheck("plotboxaspectratiomode","manual") ||
      StringCheck("dataaspectratiomode","manual")) {
    // Now we have to deal with the scale-to-fit issue.  If we
    // have scale-to-fit disabled, we get a single scale factor
    // to zoom
    double xratio = (xmax-xmin)/position[2];
    double yratio = (ymax-ymin)/position[3];
    double maxratio = qMax(xratio,yratio);
    rerange(xmin,xmax,maxratio*position[2]);
    rerange(ymin,ymax,maxratio*position[3]);
  }
  //    qDebug("Limits %f %f %f %f %f %f",
  //	   xmin,xmax,ymin,ymax,zmin,zmax);
  gc.project(xmin,xmax,ymin,ymax,-zmax,-zmin);
  gc.viewport(position[0],position[1],position[2],position[3]);

  gc.getModelviewMatrix(model);
  gc.getProjectionMatrix(proj);
  gc.getViewport(viewp);
  //     for (int i=0;i<4;i++)
  //       qDebug("%f %f %f %f",
  // 	     model[i*4],model[i*4+4],model[i*4+8],model[i*4+12]);
}

void HandleAxis::DrawBox(RenderEngine &gc) {
  // Q: Is this outerposition that's supposed to be colored?
  // Get the limits
  HPColor *hp = (HPColor*) LookupProperty("color");
  if (hp->IsNone()) return;
  std::vector<double> limits(GetAxisLimits());
  gc.color(hp->Data());
  gc.depth(false);
  gc.quad( limits[0], limits[2], limits[4],
	   limits[1], limits[2], limits[4],
	   limits[1], limits[3], limits[4],
	   limits[0], limits[3], limits[4]);
  gc.quad( limits[0], limits[2], limits[5],
	   limits[0], limits[3], limits[5],
	   limits[1], limits[3], limits[5],
	   limits[1], limits[2], limits[5]);
  gc.quad( limits[0], limits[2], limits[4],
	   limits[0], limits[3], limits[4],
	   limits[0], limits[3], limits[5],
	   limits[0], limits[2], limits[5]);
  gc.quad( limits[1], limits[2], limits[4],
	   limits[1], limits[2], limits[5],
	   limits[1], limits[3], limits[5],
	   limits[1], limits[3], limits[4]);
  gc.quad( limits[0], limits[2], limits[4],
	   limits[0], limits[2], limits[5],
	   limits[1], limits[2], limits[5],
	   limits[1], limits[2], limits[4]);
  gc.quad( limits[0], limits[3], limits[4],
	   limits[1], limits[3], limits[4],
	   limits[1], limits[3], limits[5],
	   limits[0], limits[3], limits[5]);
  
  gc.depth(true);
}


void HandleAxis::DrawGridLines(RenderEngine &gc) {
  std::vector<double> limits(GetAxisLimits());
  gc.depth(false);
  // The normals of interest are 
  // [0,0,1],[0,0,-1],
  // [0,1,0],[0,-1,0],
  // [1,0,0],[-1,0,0]
  // We will multiply the transformation matrix 
  // by a directional vector.  Then we test the
  // sign of the z component.  This sequence of
  // operations is equivalent to simply taking the
  // 2, 6, 10 elements of m, and drawing the corresponding
  // Select the set of grids to draw based on these elements
  // Draw the grid
  gc.lineWidth(ScalarPropertyLookup("linewidth"));
  gc.setLineStyle(((HPLineStyle*) LookupProperty("gridlinestyle"))->Data());
  HPVector *hp;
  hp = (HPVector*) LookupProperty("xtick");
  std::vector<double> xticks(hp->Data());
  hp = (HPVector*) LookupProperty("ytick");
  std::vector<double> yticks(hp->Data());
  hp = (HPVector*) LookupProperty("ztick");
  std::vector<double> zticks(hp->Data());
  HPColor *xc = (HPColor*) LookupProperty("xcolor");
  HPColor *yc = (HPColor*) LookupProperty("ycolor");
  HPColor *zc = (HPColor*) LookupProperty("zcolor");
  if (xvisible && ((HPOnOff*) LookupProperty("xgrid"))->AsBool()) {
    gc.color(xc->Data());
    for (int i=0;i<xticks.size();i++) {
      GLfloat t = MapX(xticks[i]);
      DrawXGridLine(gc,t,limits);
    }
  }
  if (yvisible && ((HPOnOff*) LookupProperty("ygrid"))->AsBool()) {
    gc.color(yc->Data());
    for (int i=0;i<yticks.size();i++) {
      GLfloat t = MapY(yticks[i]);
      DrawYGridLine(gc,t,limits);
    }
  }
  if (zvisible && ((HPOnOff*) LookupProperty("zgrid"))->AsBool()) {
    gc.color(zc->Data());
    for (int i=0;i<zticks.size();i++) {
      GLfloat t = MapZ(zticks[i]);
      DrawZGridLine(gc,t,limits);
    }
  }
  gc.depth(true);
}

void HandleAxis::DrawXGridLine(RenderEngine &gc, double t, 
			       std::vector<double> limits) {
  double m[16];
  gc.getModelviewMatrix(m);
  if (m[10] > 0) {
    gc.line(t,limits[2],limits[4],
	    t,limits[3],limits[4]);
  } else if (m[10] < 0) {
    gc.line(t,limits[2],limits[5],
	    t,limits[3],limits[5]);
  }
  if (m[6] > 0) {
    gc.line(t,limits[2],limits[4],
	    t,limits[2],limits[5]);
  } else if (m[6] < 0) {
    gc.line(t,limits[3],limits[4],
	    t,limits[3],limits[5]);
  }
}
  
void HandleAxis::DrawYGridLine(RenderEngine &gc, double t,
			       std::vector<double> limits) {
  double m[16];
  gc.getModelviewMatrix(m);
  if (m[10] > 0) {
    gc.line(limits[0],t,limits[4],
	    limits[1],t,limits[4]);
  } else if (m[10] < 0) {
    gc.line(limits[0],t,limits[5],
	    limits[1],t,limits[5]);
  }
  if (m[2] > 0) {
    gc.line(limits[0],t,limits[4],
	    limits[0],t,limits[5]);
  } else if (m[2] < 0) {
    gc.line(limits[1],t,limits[4],
	    limits[1],t,limits[5]);
  }
}

void HandleAxis::DrawZGridLine(RenderEngine &gc, double t,
			       std::vector<double> limits) {
  double m[16];
  gc.getModelviewMatrix(m);
  if (m[6] > 0) {
    gc.line(limits[0],limits[2],t,
	    limits[1],limits[2],t);
  } else if (m[6] < 0) {
    gc.line(limits[0],limits[3],t,
	    limits[1],limits[3],t);
  }
  if (m[2] > 0) {
    gc.line(limits[0],limits[2],t,
	    limits[0],limits[3],t);
  } else if (m[2] < 0) {
    gc.line(limits[1],limits[2],t,
	    limits[1],limits[3],t);
  }
}				 

void HandleAxis::DrawMinorGridLines(RenderEngine &gc) {
  std::vector<double> limits(GetAxisLimits());
  gc.setLineStyle(((HPLineStyle*) LookupProperty("minorgridlinestyle"))->Data());
  gc.depth(false);
  HPVector *hp;
  hp = (HPVector*) LookupProperty("xtick");
  std::vector<double> xticks(hp->Data());
  hp = (HPVector*) LookupProperty("ytick");
  std::vector<double> yticks(hp->Data());
  hp = (HPVector*) LookupProperty("ztick");
  std::vector<double> zticks(hp->Data());
  HPColor *xc = (HPColor*) LookupProperty("xcolor");
  HPColor *yc = (HPColor*) LookupProperty("ycolor");
  HPColor *zc = (HPColor*) LookupProperty("zcolor");
  HPLinearLog *sp;
  if (((HPOnOff*) LookupProperty("xminorgrid"))->AsBool()) {
    gc.color(xc->Data());
    sp = (HPLinearLog*) LookupProperty("xscale");
    if (sp->Is("linear")) {
      for (int i=0;i<xticks.size()-1;i++) {
	double t = MapX((xticks[i]+xticks[i+1])/2);
	DrawXGridLine(gc,t,limits);
      }
    } else {
      for (int i=0;i<xticks.size()-1;i++) {
	// Ticks should be in integer divisions
	double t1 = xticks[i];
	double t2 = xticks[i+1];
	if (t2 > t1) {
	  int n = 2;
	  while ((t1*n)<t2) {
	    double t = MapX(n*t1);
	    n++;
	    DrawXGridLine(gc,t,limits);
	  }
	}
      }
    }
  }
  if (((HPOnOff*) LookupProperty("yminorgrid"))->AsBool()) {
    gc.color(yc->Data());
    sp = (HPLinearLog*) LookupProperty("yscale");
    if (sp->Is("linear")) {
      for (int i=0;i<yticks.size()-1;i++) {
	GLfloat t = MapY((yticks[i]+yticks[i+1])/2);
	DrawYGridLine(gc,t,limits);
      }
    } else {
      for (int i=0;i<yticks.size()-1;i++) {
	// Ticks should be in integer divisions
	double t1 = yticks[i];
	double t2 = yticks[i+1];
	if (t2 > t1) {
	  int n = 2;
	  while ((t1*n)<t2) {
	    double t = MapY(n*t1);
	    n++;
	    DrawYGridLine(gc,t,limits);
	  }
	}
      }
    }
  }
  if (((HPOnOff*) LookupProperty("zminorgrid"))->AsBool()) {
    gc.color(zc->Data());
    sp = (HPLinearLog*) LookupProperty("zscale");
    if (sp->Is("linear")) {
      for (int i=0;i<zticks.size()-1;i++) {
	GLfloat t = MapZ((zticks[i]+zticks[i+1])/2);
	DrawZGridLine(gc,t,limits);
      }
    } else {
      for (int i=0;i<zticks.size()-1;i++) {
	// Ticks should be in integer divisions
	double t1 = zticks[i];
	double t2 = zticks[i+1];
	if (t2 > t1) {
	  int n = 2;
	  while ((t1*n)<t2) {
	    double t = MapZ(n*t1);
	    n++;
	    DrawZGridLine(gc,t,limits);
	  }
	}
      }
    }
  }
  gc.depth(true);
}
  
double HandleAxis::flipX(double t) {
  std::vector<double> limits(GetAxisLimits());
  if (t == limits[0])
    return limits[1];
  return limits[0];
}

double HandleAxis::flipY(double t) {
  std::vector<double> limits(GetAxisLimits());
  if (t == limits[2])
    return limits[3];
  return limits[2];
}

double HandleAxis::flipZ(double t) {
  std::vector<double> limits(GetAxisLimits());
  if (t == limits[4])
    return limits[5];
  return limits[4];
}

void HandleAxis::SetupAxis(RenderEngine &gc) {
  double model[16];
  gc.getModelviewMatrix(model);
  std::vector<double> limits(GetAxisLimits());
  // Query the axisproperties to set the z-position of the
  // x and y axis
  if (((HPTopBottom*)LookupProperty("xaxislocation"))->Is("bottom")) {
    x1pos[2] = limits[4];
  } else
    x1pos[2] = limits[5];
  if (((HPLeftRight*)LookupProperty("yaxislocation"))->Is("left")) {
    y1pos[2] = limits[4];
  } else
    y1pos[2] = limits[5];

  if ((model[10] > 0) && (model[6] > 0)) {
    if (x1pos[2] == limits[4])
      x1pos[1] = limits[3];
    else
      x1pos[1] = limits[2];
  } else if ((model[10] > 0) && (model[6] <= 0)) {
    if (x1pos[2] == limits[4])
      x1pos[1] = limits[2];
    else
      x1pos[1] = limits[3];
  } else if ((model[10] <= 0) && (model[6] > 0)) {
    if (x1pos[2] == limits[4])
      x1pos[1] = limits[2];
    else
      x1pos[1] = limits[3];
  } else if ((model[10] <= 0) && (model[6] <= 0)) {
    if (x1pos[2] == limits[4])
      x1pos[1] = limits[3];
    else
      x1pos[1] = limits[2];
  } 

  // There are two possibilities for where the opposite x axis is
  //   - one option is to use the opposite axis in the y direction
  //   - the other option is to use the opposite position in the z direction
  //   - we have to decide which one to use.  What we can do is take
  //   - the longer axis
  double px0, py0, px1, py1, px2, py2;
  gc.toPixels(limits[0],x1pos[1],x1pos[2],px0,py0);
  gc.toPixels(limits[0],flipY(x1pos[1]),x1pos[2],px1,py1);
  gc.toPixels(limits[0],x1pos[1],flipZ(x1pos[2]),px2,py2);
  double len1, len2;
  len1 = ((px1-px0)*(px1-px0) + (py1-py0)*(py1-py0));
  len2 = ((px2-px0)*(px2-px0) + (py2-py0)*(py2-py0));
  if ((len1 > len2) && (len1 > 0)) {
    x2pos[1] = flipY(x1pos[1]);
    x2pos[2] = x1pos[2];
  } else {
    x2pos[1] = x1pos[1];
    x2pos[2] = flipZ(x1pos[2]);
  }

  //     if (x1pos[1] == limits[3])
  //       x2pos[1] = limits[2];
  //     else if (x1pos[1] == limits[2])
  //       x2pos[1] = limits[3];

  if ((model[10] > 0) && (model[2] > 0)) {
    if (y1pos[2] == limits[4])
      y1pos[0] = limits[1];
    else
      y1pos[0] = limits[0];
  } else if ((model[10] <= 0) && (model[2] > 0)) {
    if (y1pos[2] == limits[4])
      y1pos[0] = limits[0];
    else
      y1pos[0] = limits[1];
  } else if ((model[10] > 0) && (model[2] <= 0)) {
    if (y1pos[2] == limits[4])
      y1pos[0] = limits[0];
    else
      y1pos[0] = limits[1];
  } else if ((model[10] <= 0) && (model[2] <= 0)) {
    if (y1pos[2] == limits[4])
      y1pos[0] = limits[1];
    else
      y1pos[0] = limits[0];
  } 
  gc.toPixels(y1pos[0],limits[2],y1pos[2],px0,py0);
  gc.toPixels(flipX(y1pos[0]),limits[2],y1pos[2],px1,py1);
  gc.toPixels(y1pos[0],limits[2],flipZ(y1pos[2]),px2,py2);
  len1 = ((px1-px0)*(px1-px0) + (py1-py0)*(py1-py0));
  len2 = ((px2-px0)*(px2-px0) + (py2-py0)*(py2-py0));
  if ((len1 > len2) && (len1 > 0)) {
    y2pos[0] = y1pos[0];
    y2pos[2] = flipZ(y1pos[2]);
  } else {
    y2pos[0] = flipX(y1pos[0]);
    y2pos[2] = y1pos[2];
  }

  //     if (y1pos[0] == limits[1])
  //       y2pos[0] = limits[0];
  //     else if (y1pos[0] == limits[0])
  //       y2pos[0] = limits[1];
    
  if (model[6] > 0)
    z1pos[0] = limits[1];
  else if (model[6] <= 0)
    z1pos[0] = limits[0];
  if (model[2] > 0)
    z1pos[1] = limits[2];
  else if (model[2] <= 0)
    z1pos[1] = limits[3];

  //sgn - x - y
  //111 - H - H
  //110 - L - H
  //101 - H - L
  //100 - L - L
  //011 - L - L
  //010 - H - L
  //001 - L - H
  //000 - H - H
  //
  // so, x=H if (!10 ^ 2), and y = H if (!10 ^ 6)
  if ((model[10] > 0) && (model[6] > 0) && (model[2] > 0)) {
    z2pos[0] = limits[1];
    z2pos[1] = limits[3];
  } else if ((model[10] > 0) && (model[6] > 0) && (model[2] < 0)) {
    z2pos[0] = limits[0];
    z2pos[1] = limits[3];
  } else if ((model[10] > 0) && (model[6] < 0) && (model[2] > 0)) {
    z2pos[0] = limits[1];
    z2pos[1] = limits[2];
  } else if ((model[10] > 0) && (model[6] < 0) && (model[2] < 0)) {
    z2pos[0] = limits[0];
    z2pos[1] = limits[2];
  } else if ((model[10] < 0) && (model[6] > 0) && (model[2] > 0)) {
    z2pos[0] = limits[0];
    z2pos[1] = limits[2];
  } else if ((model[10] < 0) && (model[6] > 0) && (model[2] < 0)) {
    z2pos[0] = limits[1];
    z2pos[1] = limits[2];
  } else if ((model[10] < 0) && (model[6] < 0) && (model[2] > 0)) {
    z2pos[0] = limits[0];
    z2pos[1] = limits[3];
  } else if ((model[10] < 0) && (model[6] < 0) && (model[2] < 0)) {
    z2pos[0] = limits[1];
    z2pos[1] = limits[3];
  }

  // Check for ordinal views
  // Z axis isn't visible
  if ((model[2] == 0) && (model[6] == 0)) {
    x2pos[1] = flipY(x1pos[1]);
    x2pos[2] = x1pos[2];
    y2pos[0] = flipX(y1pos[0]);
    y2pos[2] = y2pos[2];
  }
  // X axis isn't visible
  if ((model[6] == 0) && (model[10] == 0)) {
    y2pos[0] = y1pos[0];
    y2pos[2] = flipZ(y1pos[2]);
    z2pos[0] = z1pos[0];
    z2pos[1] = flipY(z1pos[1]);
  }
  // Y axis isn't visible
  if ((model[2] == 0) && (model[10] == 0)) {
    x2pos[1] = x1pos[1];
    x2pos[2] = flipZ(x1pos[2]);
    z2pos[0] = flipX(z1pos[0]);
    z2pos[1] = z1pos[1];
  }

  double x1, y1, x2, y2;
  gc.toPixels(limits[0],x1pos[1],x1pos[2],x1,y1);
  gc.toPixels(limits[1],x1pos[1],x1pos[2],x2,y2);
  xvisible = (fabs(x1-x2) > 2) || (fabs(y1-y2) > 2);
  gc.toPixels(y1pos[0],limits[2],y1pos[2],x1,y1);
  gc.toPixels(y1pos[0],limits[3],y1pos[2],x2,y2);
  yvisible = (fabs(x1-x2) > 2) || (fabs(y1-y2) > 2);
  gc.toPixels(z1pos[0],z1pos[1],limits[4],x1,y1);
  gc.toPixels(z1pos[0],z1pos[1],limits[5],x2,y2);
  zvisible = (fabs(x1-x2) > 2) || (fabs(y1-y2) > 2);  
}

bool HandleAxis::Is2DView() {
  return (!(xvisible && yvisible && zvisible));
}

void HandleAxis::DrawAxisLines(RenderEngine &gc) { 
  std::vector<double> limits(GetAxisLimits());
  HPColor *xc = (HPColor*) LookupProperty("xcolor");
  HPColor *yc = (HPColor*) LookupProperty("ycolor");
  HPColor *zc = (HPColor*) LookupProperty("zcolor");
  gc.setLineStyle("-");
  gc.lineWidth(ScalarPropertyLookup("linewidth"));
  if (xvisible) {
    gc.color(xc->Data());
    double px0, py0, px1, py1;
    gc.toPixels(limits[0],x1pos[1],x1pos[2],px0,py0);
    gc.toPixels(limits[1],x1pos[1],x1pos[2],px1,py1);
    gc.setupDirectDraw();
    gc.line(px0,py0,px1,py1);
    gc.releaseDirectDraw();
    if (Is2DView()) {
      gc.toPixels(limits[0],x2pos[1],x2pos[2],px0,py0);
      gc.toPixels(limits[1],x2pos[1],x2pos[2],px1,py1);
      gc.setupDirectDraw();
      gc.line(px0,py0,px1,py1);
      gc.releaseDirectDraw();
    }
  }
  if (yvisible) {
    gc.color(yc->Data());
    double px0, py0, px1, py1;
    gc.toPixels(y1pos[0],limits[2],y1pos[2],px0,py0);
    gc.toPixels(y1pos[0],limits[3],y1pos[2],px1,py1);
    gc.setupDirectDraw();
    gc.line(px0,py0,px1,py1);
    gc.releaseDirectDraw();
    if (Is2DView()) {
      gc.toPixels(y2pos[0],limits[2],y2pos[2],px0,py0);
      gc.toPixels(y2pos[0],limits[3],y2pos[2],px1,py1);
      gc.setupDirectDraw();
      gc.line(px0,py0,px1,py1);
      gc.releaseDirectDraw();
    }
  } 
  if (zvisible) {
    gc.color(zc->Data());
    double px0, py0, px1, py1;
    gc.toPixels(z1pos[0],z1pos[1],limits[4],px0,py0);
    gc.toPixels(z1pos[0],z1pos[1],limits[5],px1,py1);
    gc.setupDirectDraw();
    gc.line(px0,py0,px1,py1);
    gc.releaseDirectDraw();
    if (Is2DView()) {
      gc.toPixels(z2pos[0],z2pos[1],limits[4],px0,py0);
      gc.toPixels(z2pos[0],z2pos[1],limits[5],px1,py1);
      gc.setupDirectDraw();
      gc.line(px0,py0,px1,py1);
      gc.releaseDirectDraw();
    }
  }
}

// Assemble a font for the axis
void HandleAxis::UpdateAxisFont() {
  QFont::Style fstyle;
  QFont::Weight fweight;
  HPString *fontname = (HPString*) LookupProperty("fontname");
  HPFontAngle *fontangle = (HPFontAngle*) LookupProperty("fontangle");
  HPFontWeight *fontweight = (HPFontWeight*) LookupProperty("fontweight");
  HPScalar *fontsize = (HPScalar*) LookupProperty("fontsize");
  if (fontangle->Is("normal"))
    fstyle = QFont::StyleNormal;
  if (fontangle->Is("italic"))
    fstyle = QFont::StyleItalic;
  if (fontangle->Is("oblique"))
    fstyle = QFont::StyleOblique;
  if (fontweight->Is("normal"))
    fweight = QFont::Normal;
  if (fontweight->Is("bold"))
    fweight = QFont::Bold;
  if (fontweight->Is("light"))
    fweight = QFont::Light;
  if (fontweight->Is("demi"))
    fweight = QFont::DemiBold;
  // Lookup the font
  QFont fnt(fontname->Data().c_str(),(int)(fontsize->Data()[0]));
  fnt.setStyle(fstyle);
  fnt.setWeight(fweight);
  m_font = fnt;
  QFontMetrics fm(m_font);
  QRect sze(fm.boundingRect("|"));
  SetScalarDefault("textheight",sze.height());
}

int HandleAxis::GetTickCount(RenderEngine &gc,
			     double x1, double y1, double z1,
			     double x2, double y2, double z2) {
  double u1, v1, u2, v2;
  gc.toPixels(x1,y1,z1,u1,v1);
  gc.toPixels(x2,y2,z2,u2,v2);
  double axlen;
  axlen = sqrt((u2-u1)*(u2-u1) + (v2-v1)*(v2-v1));
  int numtics = (int)(qMax(2.0,axlen/25.0));
  return numtics;
}

void HandleAxis::RecalculateTicks() {
  QPixmap img(1,1);
  QPainter pnt(&img);
  HandleFigure *fig = GetParentFigure();
  unsigned width = fig->GetWidth();
  unsigned height = fig->GetHeight();
  QTRenderEngine gc(&pnt,0,0,width,height);
  SetupProjection(gc);
  // We have to calculate the tick sets for each axis...
  std::vector<double> limits(GetAxisLimits());
  std::vector<double> xticks;
  std::vector<std::string> xlabels;
  std::vector<double> yticks;
  std::vector<std::string> ylabels;
  std::vector<double> zticks;
  std::vector<std::string> zlabels;
  int xcnt, ycnt, zcnt;
  xcnt = GetTickCount(gc,limits[0],x1pos[1],x1pos[2],
		      limits[1],x1pos[1],x1pos[2]);
  ycnt = GetTickCount(gc,y1pos[0],limits[2],y1pos[2],
		      y1pos[0],limits[3],y1pos[2]);
  zcnt = GetTickCount(gc,z1pos[0],z1pos[1],limits[4],
		      z1pos[0],z1pos[1],limits[5]);
  double xStart, xStop;
  double yStart, yStop;
  double zStart, zStop;
  HPTwoVector *tp;
  HPLinearLog *lp;
  lp = (HPLinearLog*)LookupProperty("xscale");
  if (IsAuto("xlimmode")) {
    FormatAxisAuto(limits[0],limits[1],xcnt,
		   lp->Is("log"),xStart,xStop,xticks,xlabels);
    tp = (HPTwoVector*) LookupProperty("xlim");
    std::vector<double> lims; 
    if (lp->Is("linear")) {
      lims.push_back(xStart);
      lims.push_back(xStop);
    } else {
      lims.push_back(pow(10.0,xStart));
      lims.push_back(pow(10.0,xStop));
    }
    tp->Data(lims);
  } else
    FormatAxisManual(limits[0],limits[1],xcnt,
		     lp->Is("log"),xStart,xStop,xticks,xlabels);

  lp = (HPLinearLog*)LookupProperty("yscale");
  if (IsAuto("ylimmode")) {
    FormatAxisAuto(limits[2],limits[3],ycnt,
		   lp->Is("log"),yStart,yStop,yticks,ylabels);
    tp = (HPTwoVector*) LookupProperty("ylim");
    std::vector<double> lims; 
    if (lp->Is("linear")) {
      lims.push_back(yStart);
      lims.push_back(yStop);
    } else {
      lims.push_back(pow(10.0,yStart));
      lims.push_back(pow(10.0,yStop));
    }
    tp->Data(lims);
  } else
    FormatAxisManual(limits[2],limits[3],ycnt,
		     lp->Is("log"),yStart,yStop,yticks,ylabels);

  lp = (HPLinearLog*)LookupProperty("zscale");
  if (IsAuto("zlimmode")) {
    FormatAxisAuto(limits[4],limits[5],zcnt,
		   lp->Is("log"),zStart,zStop,zticks,zlabels);
    tp = (HPTwoVector*) LookupProperty("zlim");
    std::vector<double> lims; 
    if (lp->Is("linear")) {
      lims.push_back(zStart);
      lims.push_back(zStop);
    } else {
      lims.push_back(pow(10.0,zStart));
      lims.push_back(pow(10.0,zStop));
    }
    tp->Data(lims);
  } else
    FormatAxisManual(limits[4],limits[5],zcnt,
		     lp->Is("log"),zStart,zStop,zticks,zlabels);
  // Update the limits...
    
  HPVector *hp;
  HPStringSet *qp;
  if (IsAuto("xtickmode")) {
    hp = (HPVector*) LookupProperty("xtick");
    hp->Data(xticks);
  }
  if (IsAuto("xticklabelmode")) {
    qp = (HPStringSet*) LookupProperty("xticklabel");
    qp->Data(xlabels);
  }
  if (IsAuto("ytickmode")) {
    hp = (HPVector*) LookupProperty("ytick");
    hp->Data(yticks);
  }
  if (IsAuto("yticklabelmode")) {
    qp = (HPStringSet*) LookupProperty("yticklabel");
    qp->Data(ylabels);
  }
  if (IsAuto("ztickmode")) {
    hp = (HPVector*) LookupProperty("ztick");
    hp->Data(zticks);
  }
  if (IsAuto("zticklabelmode")) {
    qp = (HPStringSet*) LookupProperty("zticklabel");
    qp->Data(zlabels);
  }
}

void HandleAxis::RePackFigure() {
  int titleHeight = 0;
  int xlabelHeight = 0;
  int ylabelHeight = 0;
  int zlabelHeight = 0;
  int maxLabelHeight = 0;
  int tickHeight = 0;
  HPHandles *lbl;
  int maxTickWidth = 0;
  int maxTickHeight = 0;
  QFontMetrics fm(m_font);
  {
    lbl = (HPHandles*) LookupProperty("xlabel");
    if (!lbl->Data().empty()) {
      HandleText *fp = (HandleText*) LookupHandleObject(lbl->Data()[0]);
      xlabelHeight = fp->GetTextHeightInPixels();
    }
    HPStringSet *hp = (HPStringSet*) LookupProperty("xticklabel");
    std::vector<std::string> xlabels(hp->Data());
    for (int i=0;i<xlabels.size();i++) {
      QRect sze(fm.boundingRect(xlabels[i].c_str()));
      maxTickWidth = qMax(maxTickWidth,sze.width());
      maxTickHeight = qMax(maxTickHeight,sze.height());
    }
  }
  {
    lbl = (HPHandles*) LookupProperty("ylabel");
    if (!lbl->Data().empty()) {
      HandleText *fp = (HandleText*) LookupHandleObject(lbl->Data()[0]);
      ylabelHeight = fp->GetTextHeightInPixels();
    }
    HPStringSet *hp = (HPStringSet*) LookupProperty("yticklabel");
    std::vector<std::string> ylabels(hp->Data());
    for (int i=0;i<ylabels.size();i++) {
      QRect sze(fm.boundingRect(ylabels[i].c_str()));
      maxTickWidth = qMax(maxTickWidth,sze.width());
      maxTickHeight = qMax(maxTickHeight,sze.height());
    }
  }
  {
    lbl = (HPHandles*) LookupProperty("zlabel");
    if (!lbl->Data().empty()) {
      HandleText *fp = (HandleText*) LookupHandleObject(lbl->Data()[0]);
      zlabelHeight = fp->GetTextHeightInPixels();
    }
    HPStringSet *hp = (HPStringSet*) LookupProperty("zticklabel");
    std::vector<std::string> zlabels(hp->Data());
    for (int i=0;i<zlabels.size();i++) {
      QRect sze(fm.boundingRect(zlabels[i].c_str()));
      maxTickWidth = qMax(maxTickWidth,sze.width());
      maxTickHeight = qMax(maxTickHeight,sze.height());
    }
  }
  lbl = (HPHandles*) LookupProperty("title");
  if (!lbl->Data().empty()) {
    HandleText *fp = (HandleText*) LookupHandleObject(lbl->Data()[0]);
    titleHeight = fp->GetTextHeightInPixels();
  }
  QRect sze(fm.boundingRect("|"));
  tickHeight =  sze.height();
  // Take the maximum of the title, and label sizes to compute
  // the padding...
  maxLabelHeight = qMax(titleHeight,xlabelHeight);
  maxLabelHeight = qMax(maxLabelHeight,ylabelHeight);
  maxLabelHeight = qMax(maxLabelHeight,zlabelHeight);
  //    qDebug("titleHeight = %d, maxLabelHeight = %d",titleHeight,maxLabelHeight);
  // Get the outer position vector...
  std::vector<double> outerpos(GetPropertyVectorAsPixels("outerposition"));
  // Special case - no labels at all --> super tight packing
  HandleFigure *fig = GetParentFigure();
  unsigned width = fig->GetWidth();
  unsigned height = fig->GetHeight();
  //    qDebug("maxtickwidth = %d maxtickheight = %d maxlabelheight = %d",
  //	   maxTickWidth,maxTickHeight,maxLabelHeight);
  if ((maxTickWidth == 0) && (maxTickHeight == 0) && (maxLabelHeight == 0)) {
    //      qDebug("tight pack...\n");
    HPFourVector *hp = (HPFourVector*) LookupProperty("position");
    hp->Value(outerpos[0]/width,outerpos[1]/height,
	      outerpos[2]/width,outerpos[3]/height);
    return;
  }
  // Generate a candidate position vector based on the default
  double posx0,posy0,poswidth,posheight;
  posx0 = qMax(0.1*outerpos[2]+maxTickWidth,0.13*outerpos[2]);
  posy0 = qMax(0.1*outerpos[3]+maxTickHeight,0.11*outerpos[2]);
  poswidth = outerpos[2]-2*posx0;
  posheight = outerpos[3]-2*posy0;
  //     poswidth = qMin(0.9*outerpos[2]-2*maxTickWidth,0.775*outerpos[2]);
  //     posheight = qMin(0.815*outerpos[2],0.9*outerpos[3]-2*maxTickHeight);
  // Pad the label height
  maxLabelHeight = (int)(maxLabelHeight*1.2 + tickHeight);
  // Check posx0 against maxLabelHeight..
  if (posx0 < maxLabelHeight)
    posx0 = maxLabelHeight;
  // Check posy0 against maxLabelHeight...
  if (posy0 < maxLabelHeight)
    posy0 = maxLabelHeight;
  // Check the width against maxLabelHeight...
  if ((outerpos[2] - poswidth) < 2*maxLabelHeight) {
    poswidth = outerpos[2] - 2*maxLabelHeight;
  }
  if ((outerpos[3] - posheight) < 2*maxLabelHeight) {
    posheight = outerpos[3] - 2*maxLabelHeight;
  }
  // Normalize
  poswidth = poswidth/width;
  posheight = posheight/height;
  posx0 = (posx0+outerpos[0])/width;
  posy0 = (posy0+outerpos[1])/height;
  poswidth = qMax(0.0,poswidth);
  posheight = qMax(0.0,posheight);
  HPFourVector *hp = (HPFourVector*) LookupProperty("position");
  hp->Value(posx0,posy0,poswidth,posheight);
  //  qDebug("Pack %f %f %f %f",posx0,posy0,poswidth,posheight);
}

void HandleAxis::UpdateLimits(bool x, bool y, bool z, bool a, bool c) {
  if (!x && !y && !z && !a && !c) return;
  // Get our set of children
  std::vector<double> limits;
  bool first = true;
  HPHandles *children = (HPHandles*) LookupProperty("children");
  std::vector<unsigned> handles(children->Data());
  for (int i=0;i<handles.size();i++) {
    HandleObject *fp = LookupHandleObject(handles[i]);
    std::vector<double> child_limits(fp->GetLimits());
    if (!child_limits.empty()) {
      if (first) {
	limits = child_limits;
	first = false;
      } else {
	for (int i=0;i<qMin(limits.size(),child_limits.size());i+=2) {
	  limits[i] = qMin(limits[i],child_limits[i]);
	  limits[i+1] = qMax(limits[i+1],child_limits[i+1]);
	}
      }
    }
  }
  if (first) return;
  if (limits[1] == limits[0]) {
    limits[0] = limits[0] - 0.5;
    limits[1] = limits[0] + 1;
  }
  if (limits[3] == limits[2]) {
    limits[2] = limits[2] - 0.5;
    limits[3] = limits[2] + 1;
  }
  if (limits[5] == limits[4]) {
    limits[4] = limits[4] - 0.5;
    limits[5] = limits[4] + 1;
  }
  HPSixVector *hp = (HPSixVector*) LookupProperty("datalimits");
  hp->Value(limits[0],limits[1],limits[2],limits[3],limits[4],limits[5]);
  if (x) SetTwoVectorDefault("xlim",limits[0],limits[1]);
  if (y) SetTwoVectorDefault("ylim",limits[2],limits[3]);
  if (z) SetTwoVectorDefault("zlim",limits[4],limits[5]);
  if (c) SetTwoVectorDefault("clim",limits[6],limits[7]);
  if (a) SetTwoVectorDefault("alim",limits[8],limits[9]);
}

void HandleAxis::HandlePlotBoxFlags() {
  bool xflag, yflag, zflag, aflag, cflag;
  xflag = IsAuto("xlimmode");
  yflag = IsAuto("ylimmode");
  zflag = IsAuto("zlimmode");
  aflag = IsAuto("alimmode");
  cflag = IsAuto("climmode");

  // Check for the various cases
  bool axesauto = xflag && yflag && zflag;
  bool darauto = IsAuto("dataaspectratiomode");
  bool pbaauto = IsAuto("plotboxaspectratiomode");
  bool onemanual = (!xflag && yflag && zflag) || (xflag && !yflag && zflag) || 
    (xflag && yflag && !zflag);
  //     qDebug("axesauto = %d darauto = %d pbauto = %d onemanual = %d",
  // 	   axesauto,darauto,pbaauto,onemanual);
  std::vector<double> limits(GetAxisLimits());
  double xrange = limits[1] - limits[0];
  double yrange = limits[3] - limits[2];
  double zrange = limits[5] - limits[4];
  double minrange = qMin(xrange,qMin(yrange,zrange));
  double maxrange = qMax(xrange,qMax(yrange,zrange));
  std::vector<double> pba(VectorPropertyLookup("plotboxaspectratio"));
  double xratio = pba[0];
  double yratio = pba[1];
  double zratio = pba[2];
  double minratio = qMin(xratio,qMin(yratio,zratio));
  xratio/=minratio;
  yratio/=minratio;
  zratio/=minratio;
  std::vector<double> dar(VectorPropertyLookup("dataaspectratio"));
  double xscale = dar[0];
  double yscale = dar[1];
  double zscale = dar[2];
  double minscale = qMin(xscale,qMin(yscale,zscale));
  xscale /= minscale;
  yscale /= minscale;
  zscale /= minscale;
    
  if (darauto && pbaauto) {
    SetThreeVectorDefault("dataaspectratio",1,1,1);
    SetThreeVectorDefault("plotboxaspectratio",xrange/minrange,yrange/minrange,zrange/minrange);
  } else if (darauto && !pbaauto) {
    // Use calculated limits, set data aspect ratio to achieve the desired plot box ratio
    SetThreeVectorDefault("plotboxaspectratio",xratio,yratio,zratio);
    // xratio = xrange/xscale, so if xscale is changable, and xrange is fixed,
    SetThreeVectorDefault("dataaspectratio",xrange/xratio,yrange/yratio,zrange/zratio);
  } else if (!darauto && pbaauto) {
    SetThreeVectorDefault("dataaspectratio",xscale,yscale,zscale);
  } else {
    if (axesauto) {
      // Plot box aspect ratio has been specified, but the dataaspect ratio is
      // fixed...  Collect the various quantities...
      // PBA = xratio = xrange/xscale --> xrange = xratio/xscale
      rerange(limits[0],limits[1],xratio/xscale*maxrange);
      rerange(limits[2],limits[3],yratio/yscale*maxrange);
      rerange(limits[4],limits[5],zratio/zscale*maxrange);
      SetAxisLimits(limits);
    } else if (onemanual) {
      if (!xflag) {
	rerange(limits[2],limits[3],yratio/yscale*xrange);
	rerange(limits[4],limits[5],zratio/zscale*xrange);
	SetAxisLimits(limits);
      } else if (!yflag) {
	rerange(limits[0],limits[1],xratio/xscale*yrange);
	rerange(limits[4],limits[5],zratio/zscale*yrange);
	SetAxisLimits(limits);
      } else if (!zflag) {
	rerange(limits[0],limits[1],xratio/xscale*zrange);
	rerange(limits[2],limits[3],yratio/yscale*zrange);
	SetAxisLimits(limits);
      }
    } else {
      // Ignore plotboxaspectratio...
    }
  }
}
  
void HandleAxis::UpdateState() {
  std::vector<std::string> tset;
  if (HasChanged("xlim")) ToManual("xlimmode");
  if (HasChanged("ylim")) ToManual("ylimmode");
  if (HasChanged("zlim")) ToManual("zlimmode");
  if (HasChanged("alim")) ToManual("alimmode");
  if (HasChanged("clim")) ToManual("climmode");
  if (HasChanged("xtick")) ToManual("xtickmode");
  if (HasChanged("ytick")) ToManual("ytickmode");
  if (HasChanged("ztick")) ToManual("ztickmode");
  if (HasChanged("xticklabel")) ToManual("xticklabelmode");
  if (HasChanged("yticklabel")) ToManual("yticklabelmode");
  if (HasChanged("zticklabel")) ToManual("zticklabelmode");
  tset.push_back("fontangle");  tset.push_back("fontname");
  tset.push_back("fontsize");   tset.push_back("fontunits");
  tset.push_back("fontweight"); tset.push_back("xticklabel");
  tset.push_back("yticklabel"); tset.push_back("zticklabel");
  tset.push_back("xcolor");     tset.push_back("ycolor"); 
  tset.push_back("zcolor"); 
  if (HasChanged(tset)) {
    UpdateAxisFont();
    ClearChanged(tset);
  }
    

  // Repack the figure...
  // To repack the figure, we get the heights of the
  // three labels (title, xlabel and ylabel)
  //    RePackFigure();
  // if ticklabels changed --> tickmode = manual
  // if tickdir set --> tickdirmode = manual
  // if resize || position chng && tickmode = auto --> recalculate tick marks
  // if resize || position chng && ticlabelmode = auto --> recalculate tick labels
  HandleFigure* fig = GetParentFigure();
  if (fig->Resized() || HasChanged("position")) {
    //      RecalculateTicks();
  }
  // Limits
  bool xflag, yflag, zflag, aflag, cflag;
  xflag = IsAuto("xlimmode");
  yflag = IsAuto("ylimmode");
  zflag = IsAuto("zlimmode");
  aflag = IsAuto("alimmode");
  cflag = IsAuto("climmode");
  UpdateLimits(xflag,yflag,zflag,aflag,cflag);

  if (HasChanged("dataaspectratio")) ToManual("dataaspectratiomode");
  if (HasChanged("plotboxaspectratio")) ToManual("plotboxaspectratiomode");

  HandlePlotBoxFlags();

  // Camera properties...
  if (HasChanged("cameratarget")) 
    ToManual("cameratargetmode");
  if (IsAuto("cameratargetmode")) {
    // Default to 2D
    HPThreeVector *tv = (HPThreeVector*) LookupProperty("cameratarget");
    std::vector<double> limits(GetAxisLimits());
    tv->Value((limits[0]+limits[1])/2.0,
	      (limits[2]+limits[3])/2.0,
	      (limits[4]+limits[5])/2.0);
  }
  if (HasChanged("cameraposition"))
    ToManual("camerapositionmode");
  if (IsAuto("camerapositionmode")) {
    // Default to 2D
    HPThreeVector *tv = (HPThreeVector*) LookupProperty("cameraposition");
    std::vector<double> limits(GetAxisLimits());
    tv->Value((limits[0]+limits[1])/2.0,
	      (limits[2]+limits[3])/2.0,
	      limits[5]+1);
  }
  if (HasChanged("cameraupvector"))
    ToManual("cameraupvectormode");
  if (IsAuto("cameraupvectormode")) {
    // Default to 2D
    HPThreeVector *tv = (HPThreeVector*) LookupProperty("cameraupvector");
    tv->Value(0,1,0);
  }

  HPHandles *children = (HPHandles*) LookupProperty("children");
  std::vector<unsigned> handles(children->Data());
  for (int i=0;i<handles.size();i++) {
    HandleObject *fp = LookupHandleObject(handles[i]);
    fp->UpdateState();
  }    

  RePackFigure();
  RecalculateTicks();
  RePackFigure();
  RecalculateTicks();
  RePackFigure();
  ClearAllChanged();
  fig->Repaint();
}

// The orientation of the label depends on the angle of the
// tick
void HandleAxis::DrawLabel(RenderEngine& gc,
			   double dx, double dy, 
			   double x2, double y2, 
			   std::vector<double> color,
			   std::string txt) {
  double angle = atan2(dy,dx)*180.0/M_PI;
  RenderEngine::AlignmentFlag xalign;
  RenderEngine::AlignmentFlag yalign;
  if (fabs(angle) < 10) {
    xalign = RenderEngine::Min;
    yalign = RenderEngine::Mean;
  } else if (fabs(angle) > 170) {
    xalign = RenderEngine::Max;
    yalign = RenderEngine::Mean;
  } else if ((angle >= 10) && (angle < 80)) {
    xalign = RenderEngine::Min;
    yalign = RenderEngine::Min;
  } else if ((angle >= 80) && (angle < 100)) {
    xalign = RenderEngine::Mean;
    yalign = RenderEngine::Min;
  } else if ((angle >= 100) && (angle < 170)) {
    xalign = RenderEngine::Max;
    yalign = RenderEngine::Min;
  } else if ((angle <= -10) && (angle > -80)) {
    xalign = RenderEngine::Min;
    yalign = RenderEngine::Max;
  } else if ((angle <= -80) && (angle > -100)) {
    xalign = RenderEngine::Mean;
    yalign = RenderEngine::Max;
  } else if ((angle <= -100) && (angle > -170)) {
    xalign = RenderEngine::Max;
    yalign = RenderEngine::Max;
  }
  gc.setupDirectDraw();    
  gc.putText(x2,y2,txt,color,xalign,yalign,m_font,0);
  gc.releaseDirectDraw();
}

//
// Look at the z axis... if T*[0;0;1;0] y component is positive,
// put x and y axis at the bottom.  otherwise, put them at the top.
//
void HandleAxis::DrawTickMarks(RenderEngine &gc) {
  // The trick here is to determine where to attach the 
  // three axes - there should be two possibilities for
  // each axis.  We start with four possibilities for
  // each axis.  Now each axis sits on the boundary of
  // two facets.  If exactly one of the two facets is
  // visible, then the axis line is visible.
  HandleFigure *fig = GetParentFigure();
  unsigned width = fig->GetWidth();
  unsigned height = fig->GetHeight();    
  HPVector *hp;
  hp = (HPVector*) LookupProperty("xtick");
  std::vector<double> xticks(hp->Data());
  hp = (HPVector*) LookupProperty("ytick");
  std::vector<double> yticks(hp->Data());
  hp = (HPVector*) LookupProperty("ztick");
  std::vector<double> zticks(hp->Data());
  gc.lineWidth(ScalarPropertyLookup("linewidth"));
  HPColor *xc = (HPColor*) LookupProperty("xcolor");
  HPColor *yc = (HPColor*) LookupProperty("ycolor");
  HPColor *zc = (HPColor*) LookupProperty("zcolor");
  // Compute the longest 
  std::vector<double> position(GetPropertyVectorAsPixels("position"));
  int maxlen = (int)((position[2] > position[3]) ? position[2] : position[3]);
  HPTwoVector *kp = (HPTwoVector*) LookupProperty("ticklength");
  std::vector<double> ticklen(kp->Data());
  int ticlen;
  if (Is2DView())
    ticlen = (int) (maxlen*ticklen[0]);
  else
    ticlen = (int) (maxlen*ticklen[1]);
  float ticdir;
  if (IsAuto("tickdirmode")) {
    if (Is2DView())
      ticdir = 1;
    else
      ticdir = -1;
  } else {
    if (((HPInOut*) LookupProperty("tickdir"))->Is("in")) 
      ticdir = 1;
    else
      ticdir = -1;
  }
  HPStringSet *qp;
  qp = (HPStringSet*) LookupProperty("xticklabel");
  std::vector<std::string> xlabeltxt(qp->Data());
  qp = (HPStringSet*) LookupProperty("yticklabel");
  std::vector<std::string> ylabeltxt(qp->Data());
  qp = (HPStringSet*) LookupProperty("zticklabel");
  std::vector<std::string> zlabeltxt(qp->Data());
  // Draw the ticks
  std::vector<double> limits(GetAxisLimits());
  // Next step - calculate the tick directions...
  // We have to draw the tics in flat space
  //
  // To keep the label from touching the tick labels, we need
  // that the tick label box (x0,y0) --> (x0+maxx,y0+maxy)
  // and we need to advance by (x0+n*dx,y0+n*dy) so that
  // n = max(maxx/dx,maxy/dy)
  //
  gc.setLineStyle("-");
  std::vector<double> outerpos(GetPropertyVectorAsPixels("outerposition"));
  if (xvisible) {
    std::vector<double> mapticks;
    for (int i=0;i<xticks.size();i++)
      mapticks.push_back(MapX(xticks[i]));
    std::vector<double> minorticks;
    HPLinearLog *sp;
    sp = (HPLinearLog*) LookupProperty("xscale");
    if (sp->Is("log")) {
      for (int i=0;i<xticks.size()-1;i++) {
	double t1 = xticks[i];
	double t2 = xticks[i+1];
	int n = 2;
	while (((t1*n) < t2) && (n < MAX_TICK_COUNT)) {
	  minorticks.push_back(MapX(n*t1));
	  n++;
	}
      }
    }
    DrawTickLabels(gc,xc->Data(),
		   0,x1pos[1],x1pos[2],
		   0,x2pos[1],x2pos[2],
		   limits[0],limits[1],
		   1,0,0,
		   mapticks,minorticks,xlabeltxt,
		   "xlabel",ticlen,ticdir);
  }
  if (yvisible) {
    std::vector<double> mapticks;
    for (int i=0;i<yticks.size();i++)
      mapticks.push_back(MapY(yticks[i]));
    std::vector<double> minorticks;
    HPLinearLog *sp;
    sp = (HPLinearLog*) LookupProperty("yscale");
    if (sp->Is("log")) {
      for (int i=0;i<yticks.size()-1;i++) {
	double t1 = yticks[i];
	double t2 = yticks[i+1];
	int n = 2;
	while (((t1*n) < t2) && (n < MAX_TICK_COUNT)) {
	  minorticks.push_back(MapY(n*t1));
	  n++;
	}
      }
    }
    DrawTickLabels(gc,yc->Data(),
		   y1pos[0],0,y1pos[2],
		   y2pos[0],0,y2pos[2],
		   limits[2],limits[3],
		   0,1,0,
		   mapticks,minorticks,ylabeltxt,
		   "ylabel",ticlen,ticdir);
  }
  if (zvisible) {
    std::vector<double> mapticks;
    for (int i=0;i<zticks.size();i++)
      mapticks.push_back(MapZ(zticks[i]));
    std::vector<double> minorticks;
    HPLinearLog *sp;
    sp = (HPLinearLog*) LookupProperty("zscale");
    if (sp->Is("log")) {
      for (int i=0;i<zticks.size()-1;i++) {
	double t1 = zticks[i];
	double t2 = zticks[i+1];
	int n = 2;
	while (((t1*n) < t2) && (n < MAX_TICK_COUNT)) {
	  minorticks.push_back(MapZ(n*t1));
	  n++;
	}
      }
    }
    DrawTickLabels(gc,zc->Data(),
		   z1pos[0],z1pos[1],0,
		   z2pos[0],z2pos[1],0,
		   limits[4],limits[5],
		   0,0,1,
		   mapticks,minorticks,zlabeltxt,
		   "zlabel",ticlen,ticdir);
  }
  HPHandles *lbl = (HPHandles*) LookupProperty("title");
  //     if (!lbl->Data().empty()) {
  //       HandleText *fp = handleset.lookupHandle(lbl->Data()[0]);
  //       HPThreeVector *gp = (HPThreeVector*) fp->LookupProperty("position");
  //       // Put the title in the right spot
  //       //      fp->PaintMe(gc);
  //     }
}

void HandleAxis::DrawTickLabels(RenderEngine& gc,
				std::vector<double> color,
				double px1, double py1, double pz1,
				double px2, double py2, double pz2,
				double limmin, double limmax,
				double unitx, double unity, double unitz,
				std::vector<double>  maptics,
				std::vector<double>  minortics,
				std::vector<std::string> labels,
				std::string labelname,
				int ticlen, double ticdir) {
  gc.color(color);
  // Calculate the tick direction vector
  double dx1, dy1, dx2, dy2;
  gc.debug();
  gc.toPixels(limmin*unitx+px1,
	      limmin*unity+py1,
	      limmin*unitz+pz1,dx1,dy1);
  gc.toPixels(limmin*unitx+px2,
	      limmin*unity+py2,
	      limmin*unitz+pz2,dx2,dy2);
  //    qDebug("Axis %s start %f,%f --> %f,%f",labelname.c_str(),dx1,dy1,dx2,dy2);
  double delx, dely;
  delx = dx2-dx1; dely = dy2-dy1;
  // normalize the tick length
  double norm = sqrt(delx*delx + dely*dely);
  delx /= norm; dely /= norm;
  // Draw the minor ticks
  for (int i=0;i<minortics.size();i++) {
    double t = minortics[i];
    // Map the coords ourselves
    double x1, y1, x2, y2;
    gc.toPixels(t*unitx+px1,
		t*unity+py1,
		t*unitz+pz1,x1,y1);
    x2 = delx*ticlen*ticdir*0.6 + x1;
    y2 = dely*ticlen*ticdir*0.6 + y1;
    gc.setupDirectDraw();
    gc.line(x1,y1,x2,y2);
    gc.releaseDirectDraw();
  }
  for (int i=0;i<maptics.size();i++) {
    double t = maptics[i];
    // Map the coords ourselves
    double x1, y1, x2, y2;
    gc.toPixels(t*unitx+px1,
		t*unity+py1,
		t*unitz+pz1,x1,y1);
    x2 = delx*ticlen*ticdir + x1;
    y2 = dely*ticlen*ticdir + y1;
    gc.setupDirectDraw();
    gc.line(x1,y1,x2,y2);
    gc.releaseDirectDraw();
    double x3, y3;
    if (ticdir > 0) {
      x3 = -delx*0.015*norm + x1;
      y3 = -dely*0.015*norm + y1;
    } else {
      x3 = -delx*0.015*norm + x2;
      y3 = -dely*0.015*norm + y2;
    }
    if (!labels.empty()) {
      DrawLabel(gc,-delx,-dely,x3,y3,color,
		labels[i % labels.size()]);
    }
    // For a 2D view, draw the opposite tick marks too
    if (Is2DView()) {
      gc.toPixels(t*unitx+px2,
		  t*unity+py2,
		  t*unitz+pz2,x1,y1);
      x2 = -delx*ticlen*ticdir + x1;
      y2 = -dely*ticlen*ticdir + y1;
      gc.setupDirectDraw();
      gc.line(x1,y1,x2,y2);
      gc.releaseDirectDraw();
    }
  }
  // Get the maximum tick metric
  double maxx, maxy;
  GetMaxTickMetric(gc,labels,maxx,maxy);
  // Draw the x label
  // Calculate the center of the x axis...
  double x1, x2, x3, y1, y2, y3;
  double meanval;
  meanval = (limmin+limmax)/2.0;
  gc.toPixels(meanval*unitx+px1,
	      meanval*unity+py1,
	      meanval*unitz+pz1,x1,y1);
  // Calculate the tick offset
  x2 = delx*ticlen*ticdir + x1;
  y2 = dely*ticlen*ticdir + y1;
  // Offset by the top of the label
  if (ticdir > 0) {
    x3 = -delx*0.015*norm + x1;
    y3 = -dely*0.015*norm + y1;
  } else {
    x3 = -delx*0.015*norm + x2;
    y3 = -dely*0.015*norm + y2;
  }
  double lx, ly;
  if (delx != 0)
    lx = fabs(maxx/delx);
  else
    lx = 1e10;
  if (dely != 0)
    ly = fabs(maxy/dely);
  else
    ly = 1e10;
  double lmax;
  lmax = qMin(lx,ly);
  // Set the position of the label
  HPHandles *lbl = (HPHandles*) LookupProperty(labelname);
  if (!lbl->Data().empty()) {
    HandleText *fp = (HandleText*) LookupHandleObject(lbl->Data()[0]);
    // To calculate the angle, we have to look at the axis
    // itself.  The direction of the axis is determined by
    // the projection of [1,0,0] onto the screen plane
    double axx1, axy1, axx2,axy2;
    gc.toPixels(0,0,0,axx1,axy1);
    gc.toPixels(unitx,unity,unitz,axx2,axy2);
    double angle = atan2(axy2-axy1,axx2-axx1)*180.0/M_PI;
    if (angle < -90) angle += 180;
    if (angle > 90) angle -= 180;
    HPScalar *sp = (HPScalar*) fp->LookupProperty("rotation");
    // The angle we want is no the rotation angle of the axis, but
    // the angle of the origin to label position.  We get this
    // taking the mean limit along the unit direction, and the
    // average of the two projected axes.
    double origx, origy;
    gc.toPixels(meanval*unitx+(px1+px2)/2.0,
		meanval*unity+(py1+py2)/2.0,
		meanval*unitz+(pz1+pz2)/2.0,origx,origy);
    double meanx, meany;
    gc.toPixels(meanval*unitx+px1,
		meanval*unity+py1,
		meanval*unitz+pz1,meanx,meany); 
     
    //       int pixpad = 1.5*fp->GetTextHeightInPixels();
    //       // Offset by the labelsize
    //       if (lmax == lx)
    // 	lmax += fabs(pixpad/delx);
    //       else
    // 	lmax += fabs(pixpad/dely);
    double xl1, yl1;
    xl1 = x3 - lmax*delx;
    yl1 = y3 - lmax*dely;
    double angle2 = atan2(y3-origy,x3-origx)*180.0/M_PI;
    if ((angle == 90) && (angle2 > -90)) {
      angle = -90;
    }
    if (angle2 == 180)
      angle2 = -180;
    if (angle2 < 0)
      if (fabs(angle) != 90)
	((HPAlignVert *) fp->LookupProperty("verticalalignment"))->Value("top");
      else
	((HPAlignVert *) fp->LookupProperty("verticalalignment"))->Value("bottom");
    else
      ((HPAlignVert *) fp->LookupProperty("verticalalignment"))->Value("bottom");
    if ((angle == -90) && (angle2 == -180))
      angle = 90;
    sp->Value(angle);
    // Move another couple of percent along the radial line
    xl1 += (x3-origx)*0.04;
    yl1 += (y3-origy)*0.04;
    HPThreeVector *gp = (HPThreeVector*) fp->LookupProperty("position");
    // We now have the position of the label in absolute (pixel)
    // coordinates.  Need to translate this to normalized coordinates
    // relative to outerposition.
    std::vector<double> outerpos(GetPropertyVectorAsPixels("outerposition"));
    double xnorm, ynorm;
    xnorm = (xl1-outerpos[0])/outerpos[2];
    ynorm = (yl1-outerpos[1])/outerpos[3];
    HandleFigure *fig = GetParentFigure();
    gp->Value(xnorm,ynorm,0.0);
  }      
}

void HandleAxis::DrawAxisLabels(RenderEngine& gc) {
  // Set up the "annotation axis"
  gc.lookAt(0,0,1,0.0,0.0,0,0,1,0);
  gc.project(0,1,0,1,-1,1);
  std::vector<double> outerpos(GetPropertyVectorAsPixels("outerposition"));
  gc.viewport(outerpos[0],outerpos[1],outerpos[2],outerpos[3]);
  HPHandles *lbl;
  std::string xdir(StringPropertyLookup("xdir"));
  std::string ydir(StringPropertyLookup("ydir"));
  SetStringDefault("xdir","normal");
  SetStringDefault("ydir","normal");
  std::string xscale(StringPropertyLookup("xscale"));
  std::string yscale(StringPropertyLookup("yscale"));
  SetStringDefault("xscale","linear");
  SetStringDefault("yscale","linear");
  if (xvisible) {
    lbl = (HPHandles*) LookupProperty("xlabel");
    if (!lbl->Data().empty()) {
      HandleObject *fp = LookupHandleObject(lbl->Data()[0]);
      fp->PaintMe(gc);
    }
  }
  if (yvisible) {
    lbl = (HPHandles*) LookupProperty("ylabel");
    if (!lbl->Data().empty()) {
      HandleObject *fp = LookupHandleObject(lbl->Data()[0]);
      fp->PaintMe(gc);
    }      
  }
  if (zvisible) {
    lbl = (HPHandles*) LookupProperty("zlabel");
    if (!lbl->Data().empty()) {
      HandleObject *fp = LookupHandleObject(lbl->Data()[0]);
      fp->PaintMe(gc);
    }      
  }
  lbl = (HPHandles*) LookupProperty("title");
  if (!lbl->Data().empty()) {
    HandleObject *fp = LookupHandleObject(lbl->Data()[0]);
    fp->PaintMe(gc);
  }      
  SetupProjection(gc);
  SetStringDefault("xdir",xdir);
  SetStringDefault("ydir",ydir);
  SetStringDefault("xscale",xscale);
  SetStringDefault("yscale",yscale);
}

void HandleAxis::DrawChildren(RenderEngine& gc) {
  HPHandles *children = (HPHandles*) LookupProperty("children");
  std::vector<unsigned> handles(children->Data());
  for (int i=0;i<handles.size();i++) {
    HandleObject *fp = LookupHandleObject(handles[i]);
    fp->PaintMe(gc);
  }
}

void HandleAxis::PaintMe(RenderEngine& gc) {
  if (GetParentFigure() == NULL) return;
  SetupProjection(gc);
  SetupAxis(gc);
  if (StringCheck("visible","on")) {
    DrawBox(gc);
    DrawGridLines(gc);
    DrawMinorGridLines(gc);
  }
  std::vector<double> limits(GetAxisLimits());
  gc.setClipBox(limits);
  DrawChildren(gc);
  if (StringCheck("visible","on")) {
    DrawAxisLines(gc);
    DrawTickMarks(gc);
    DrawAxisLabels(gc);
  }
}


syntax highlighted by Code2HTML, v. 0.9.1