#delim ;
prog logaxis, rclass;
version 10.0;
/*
  Generate regular logarithmic axis range and tick mark list
  for a list of variables and/or included constants.
*! Author: Roger Newson
*! Date: 20 April 2016
*/
syntax [ varlist(numeric default=none) ] [if] [in] [ , Base(numlist max=1 >1) Scalefactors(numlist >0) MARginfactors(numlist max=2 >=1)
  INClude(numlist >0) MAXTicks(passthru) SINgleok
  LRAnge(name local) LTIcks(name local) LRMIn(name local) LRMAx(name local) LTMIn(name local) LTMAx(name local)
  LNTick(name local) LVMIn(name local) LVMAx(name local)
  GRAnge(name) GTIcks(name) GRMIn(name) GRMAx(name) GTMIn(name) GTMAx(name)
  GNTIck(name) GVMIn(name) GVMAx(name) ];
/*
-base()- specifies the logarithmic base for the tick marks (defaulting to natural logs).
-scalefactors()- specifies a list of scale factors,
  generating tick marks with values equal to scale_i*k^base,
  where k is an integer and scale_i is a scale factor.
-marginfactors()- specifies the 2 margin factors
  defining the ratio of the minimum tick to the range minimum
  and the ratio of the range maximum to the maximum tick.
-include()- specifies a list of numeric constants
  to be included in the range spanned by the ticks.
-maxticks()- specifies the maximum number of ticks.
-singleok- specifies that the output tick list can be a single tick
  if the data range contains only a single value equal to a candidate tick position.
-lrange()- is the name of a local macro to store the range.
-lticks()- is the name of a local macro to store the ticks.
-lrmin()- is the name of a local macro to store the range minimum.
-lrmax()- is the name of a local macro to store the range maximum.
-ltmin()- is the name of a local macro to store the minimum tick.
-ltmax()- is the name of a local macro to store the maximum tick.
-lntick()- is the name of a local macro to store the number of ticks.
-lvmin()- is the name of a local macro to store the minimum value present in the -varlist- and/or the -include()- option.
-lvmax()- is the name of a local macro to store the maximum value present in the -varlist- and/or the -include()- option.
-grange()- is the name of a global macro to store the range.
-gticks()- is the name of a global macro to store the ticks.
-grmin()- is the name of a global macro to store the range minimum.
-grmax()- is the name of a global macro to store the range maximum.
-gtmin()- is the name of a global macro to store the minimum tick.
-gtmax()- is the name of a global macro to store the maximum tick.
-gntick()- is the name of a global macro to store the number of ticks.
-gvmin()- is the name of a global macro to store the minimum value present in the -varlist- and/or the -include()- option.
-gvmax()- is the name of a global macro to store the maximum value present in the -varlist- and/or the -include()- option.
*/

*
 Check for illegal option values
 and generate default option values if necessary
*;
if "`scalefactors'"=="" {;
  local scalefactors=1;
};
if "`marginfactors'"=="" {;
  local marginfactors "1 1";
};
else {;
  local nmarg: word count `marginfactors';
  if `nmarg'==1 {;
    local marginfactors "`marginfactors' `marginfactors'";
  };
};

*
  Find minimum and maximum values in data and -include()- option
*;
tempname vmin vmax vmincur vmaxcur;
scal `vmin'=.;
scal `vmax'=.;
* Variables *;
if "`varlist'"!="" {;
  marksample touse, novarlist;
  foreach X of var `varlist' {;
    qui summ `X' if `touse', meanonly;
    if r(min)<`vmin' | missing(`vmin') {;scal `vmin'=r(min);};
    if r(max)>`vmax' | missing(`vmax') {;scal `vmax'=r(max);};
  };
};
* Additional constants *;
if "`include'"!="" {;
  foreach X of numlist `include' {;
    if `X'<`vmin' | missing(`vmin') {;scal `vmin'=`X';};
    if `X'>`vmax' | missing(`vmax') {;scal `vmax'=`X';};
  };
};
* Set to zero if all values missing *;
if missing(`vmin') {;scal `vmin'=1;};
if missing(`vmax') {;scal `vmax'=1;};
* Fail if nonpositive values present *;
if `vmin'<=0 | `vmax'<=0 {;
  error 411;
};

* Generate logged -include- list *;
local linclude "";
foreach X in `vmin' `vmax' {;
  if "`base'"!="" {;local vtemp=log(`X')/log(`base');};
  else {;local vtemp=log(`X');};
  local linclude "`linclude' `vtemp'";
};
local linclude "include(`linclude')";

* Generate logged margins *;
local lmargins "";
if "`marginfactors'"!="" {;
  tempname margcur;
  foreach X of numlist `marginfactors' {;
    scal `margcur'=log(`X');
    if "`base'"!="" {;
      scal `margcur'=`margcur'/log(`base');
    };
    local lmargcur=`margcur';
    local lmargins "`lmargins' `lmargcur'";
  };
  local lmargins "margins(`lmargins')";
};

* Generate logged scales *;
local lscales="";
if "`scalefactors'"!="" {;
  tempname scalecur;
  foreach X of numlist `scalefactors' {;
    scal `scalecur'=log(`X');
    if "`base'"!="" {;
      scal `scalecur'=`scalecur'/log(`base');
    };
    local lscalecur=`scalecur';
    local lscales "`lscales' `lscalecur'";
  };
  local lscales "phases(`lscales')";
};

* Generate logged axis and range *;
regaxis, cycle(1) `lscales' `lmargins' `linclude' `maxticks' `singleok';
tempname rmin rmax tmin tmax expcur;
local ntick=r(ntick);
foreach X in range ticks {;
  local `X'="";
  foreach Y of numlist `r(`X')' {;
    if "`base'"!="" {;scal `expcur'=`base'^`Y';};
    else {;scal `expcur'=exp(`Y');};
    local lexpcur=`expcur';
    local `X' "``X'' `lexpcur'";
  };
};
local temp1: word 1 of `range';
scal `rmin'=`temp1';
local temp1: word 2 of `range';
scal `rmax'=`temp1';
local temp1: word 1 of `ticks';
scal `tmin'=`temp1';
local temp1: word `ntick' of `ticks';
scal `tmax'=`temp1';

*
  Return results
*;
return scalar vmax=`vmax';
return scalar vmin=`vmin';
return scalar ntick=`ntick';
return scalar tmax=`tmax';
return scalar tmin=`tmin';
return scalar rmax=`rmax';
return scalar rmin=`rmin';
return local ticks "`ticks'";
return local range "`range'";

*
  Set local and global macro options
*;
foreach X in range ticks {;
  if "`l`X''"!="" {;
    c_local `l`X'' "``X''";
  };
  if "`g`X''"!="" {;
    global `g`X'' "``X''";
  };
};
foreach X in rmin rmax tmin tmax ntick vmin vmax {;
  if "`l`X''"!="" {;
    c_local `l`X''=``X'';
  };
  if "`g`X''"!="" {;
    global `g`X''=``X'';
  };
};

end;