% AXES: Axis/grid-tick formatter, for the METAPLOT package.
% Copyright(C) 2004, Brooks Moses
% 
%   This work may be distributed and/or modified under the
%   conditions of the LaTeX Project Public License, either
%   version 1.3 of this license or (at your option) any
%   later version.
%   The latest version of the license is in
%      http://www.latex-project.org/lppl.txt
%   and version 1.3 or later is part of all distributions of
%   LaTeX version 2003/06/01 or later.
% 
%   This work has the LPPL maintenance status "author-maintained".
%
% Version 0.9.
%
% The n < 1.0 release number indicates that this is a beta release;
% the author at present makes no assurances that the command syntax
% will remain unchanged between this and the final Version 1.0
% release.
%
% Bug reports, feature requests, and all other suggestions should
% be directed to Brooks Moses, bmoses@stanford.edu.


input format

% axes_tick(pair tickorigin,
%           numeric ticklength,
%           pair tickdir)
%
% Draws a grid tick, located on tickorigin, in the direction indicated by
% tickdir, with length ticklength.

def axes_tick(expr tickorigin, ticklength, tickdir) =
  begingroup
  interim linecap := butt;
  save theaxistick; picture theaxistick; theaxistick := nullpicture;
  addto theaxistick doublepath ((0,0)--(ticklength * unitvector(tickdir)));

  theaxistick shifted tickorigin
  endgroup
enddef;


% axes_ticklabeled(pair tickorigin,
%                  numeric ticklength,
%                  numeric tickspace,
%                  pair tickdir,
%                  picture ticklabel)
%
% Draws a grid tick, located on tickorigin, in the direction indicated by
% tickdir, with length ticklength.  In addition, ticklabel is placed as
% a label such that its center is along the line of the tick, and the
% bounding box is a distance tickspace from the end of the tick.

def axes_ticklabeled(expr tickorigin, ticklength, tickspace, tickdir, ticklabel) =
  begingroup
  interim linecap := butt;
  save tickunitvector; pair tickunitvector;
  tickunitvector := unitvector(tickdir);
  
  % We calculate an intersection point between the bbox of ticklabel
  % and a ray extending from center(ticklabel) in the direction of 
  % tickdir (approximated by a line guaranteed to be long enough to 
  % exit the bbox), in the coordinates of ticklabel.  Thus, shifting
  % ticklabel by the negative of this amount will put this intersection
  % point at (0,0).
  
  save thelabelshift; pair thelabelshift;
  thelabelshift =
    (center(ticklabel) -- (center(ticklabel)
      - tickunitvector * (xpart(urcorner ticklabel - llcorner ticklabel)
                          + ypart(urcorner ticklabel - llcorner ticklabel))))
     intersectionpoint (llcorner ticklabel -- lrcorner ticklabel
                         -- urcorner ticklabel -- ulcorner ticklabel -- cycle);
  
  save thetick; picture thetick; thetick := nullpicture;
  addto thetick doublepath ((0,0)--(ticklength * tickunitvector));
  addto thetick also ticklabel
    shifted ((ticklength + tickspace)*tickunitvector - thelabelshift);

  thetick shifted tickorigin
  endgroup
enddef;


% axes_ticknumbered(pair tickorigin,
%                   numeric ticklength,
%                   numeric tickspace,
%                   pair tickdir,
%                   numeric tickvalue)
%                   string ticklabelformat)
%
% Draws a grid tick with a formatted number (tickvalue) as the label,
% according to the format given in ticklabelformat, with the remainder
% of the syntax identical to axes_ticklabeled.

def axes_ticknumbered(expr tickorigin, ticklength, tickspace, tickdir,
                      tickvalue, ticklabelformat) =
  axes_ticklabeled(tickorigin, ticklength, tickspace, tickdir,
                   format(ticklabelformat, tickvalue))    
enddef;


% axes_tickrow(pair startpoint,
%              pair endpoint,
%              numeric ticklength,
%              pair tickdir,
%              numeric ntickspaces)
%
% Draws a row of ntickspaces+1 grid ticks (that is, ticks with ntickspaces
% spaces between them) from startpoint to endpoint, with parameters given
% by ticklength and tickdir.

def axes_tickrow(expr startpoint, endpoint,
                      ticklength, tickdir, ntickspaces) =
  begingroup
  save thetickrow; picture thetickrow; thetickrow := nullpicture;
  save tickpoint; pair tickpoint;
  for itick=0 upto ntickspaces:
    tickpoint := (itick/ntickspaces) * (endpoint - startpoint) + startpoint;
    addto thetickrow also
      axes_tick(tickpoint, ticklength, tickdir);
  endfor
  thetickrow
  endgroup
enddef;


% axes_tickrow_numbered(pair startpoint,
%                       pair endpoint,
%                       numeric ticklength,
%                       numeric tickspace,
%                       pair tickdir,
%                       numeric startvalue,
%                       numeric endvalue,
%                       numeric ntickspaces,
%                       string ticklabelformat)
%
% Draws a row of ntickspaces+1 labeled grid ticks (that is, ticks with
% ntickspaces spaces between them) from startpoint to endpoint, with
% parameters given by ticklength, tickspace, and tickdir.  These ticks
% are labeled with numeric values that range from startvalue (at'
% startpoint) to endvalue (at endpoint), according to the format given
% in ticklabelformat.

def axes_tickrow_numbered(expr startpoint, endpoint,
                               ticklength, tickspace, tickdir,
                               startvalue, endvalue, ntickspaces,
                               ticklabelformat) =
  begingroup
  save thetickrow; picture thetickrow; thetickrow := nullpicture;
  save tickpoint; pair tickpoint;
  save tickvalue; numeric tickvalue;
  for itick=0 upto ntickspaces:
    tickpoint := (itick/ntickspaces) * (endpoint - startpoint) + startpoint;
    tickvalue := (itick/ntickspaces) * (endvalue - startvalue) + startvalue;
    addto thetickrow also
      axes_ticknumbered(tickpoint, ticklength, tickspace, tickdir,
                        tickvalue, ticklabelformat);
  endfor
  thetickrow
  endgroup
enddef;


% axes_tickscale(pair startpoint,
%                pair endpoint,
%                numeric ticklength,
%                numeric tickspace,
%                pair tickdir,
%                numeric startvalue,
%                numeric endvalue,
%                numeric tickzero,
%                numeric tickstep,
%                string ticklabelformat)
%
% Draws a row of grid ticks from startpoint to endpoint, arranged
% such that their values increase in steps of size tickstep, and 
% located so that the sequence of grid ticks, if continued to 
% infinity, will include a tick with value tickzero.  The ticks
% will be labeled according to format ticklabelformat, or if 
% ticklabelformat is "", they will be unlabeled.

def axes_tickscale(expr startpoint, endpoint,
                        ticklength, tickspace, tickdir,
                        startvalue, endvalue, tickzero, tickstep,
                        ticklabelformat) =
  begingroup
  % tickepsilon is a small tolerance value to avoid roundoff errors.
  save tickepsilon; numeric tickepsilon;
  tickepsilon = 0.005 * min(1.0, abs(endvalue-startvalue)/tickstep);

  save firstticknumber; numeric firstticknumber;
  save lastticknumber; numeric lastticknumber;  
  firstticknumber = ceiling((min(startvalue,endvalue)-tickzero)/tickstep - tickepsilon);
  lastticknumber = floor((max(startvalue,endvalue)-tickzero)/tickstep + tickepsilon);

  save firsttickvalue; numeric firsttickvalue;
  save lasttickvalue; numeric lasttickvalue;  
  firsttickvalue = firstticknumber * tickstep + tickzero;
  lasttickvalue = lastticknumber * tickstep + tickzero;

  if ticklabelformat="":
    axes_tickrow( 
      ((firsttickvalue-startvalue)/(endvalue-startvalue))[startpoint, endpoint],
      ((lasttickvalue-startvalue)/(endvalue-startvalue))[startpoint, endpoint],
      ticklength, tickdir, lastticknumber - firstticknumber)
  else:
    axes_tickrow_numbered( 
      ((firsttickvalue-startvalue)/(endvalue-startvalue))[startpoint, endpoint],
      ((lasttickvalue-startvalue)/(endvalue-startvalue))[startpoint, endpoint],
      ticklength, tickspace, tickdir, 
      firsttickvalue, lasttickvalue, lastticknumber - firstticknumber,
      ticklabelformat)
  fi
  endgroup
enddef;