#delim ;
prog def eclplot, rclass;
version 13.0;
/*
  Create plot of estimates and confidence limits
  (vertical or horizontal)
  against a numeric parameter ID variable..
*! Author: Roger Newson
*! Date: 06 March 2021
*/

*
 Define maximum number of estimate and confidence limit plots
 and lists of options for these estimates and confidence limits plots
*;
local msupby=15;
local estoptsi "";
local cioptsi "";
forv i1=1(1)`msupby' {;
  local estoptsi `"`estoptsi' ESTOPTS`i1'(string asis)"';
  local cioptsi `"`cioptsi' CIOPTS`i1'(string asis)"';
};
* Parse syntax *;
syntax varlist(numeric min=4 max=4) [if] [in] [, HORizontal
  EPLOTtype(string) RPLOTtype(string) noCIForeground
  ESTOPTS(string asis) CIOPTS(string asis)
  ADDPLOT(string asis) BADDPLOT(string asis) PLOT(string asis) noGRaph
  BY(string asis)
  supby(string asis)
  `estoptsi' `cioptsi'
  * ];
/*
 -varlist- is a list of 4 variables
  (estimate, lower confidence limit, upper confidence limit,
  and numeric parameter ID variable to plot them against).
 -horizontal- specifies that a horizontal CI plot will be drawn
  (instead of the default vertical CI plot).
 -eplottype- specifies the plot typr used to draw the estimates
   (defaulting to -scatter- to give unconnected symbols
   and no areas, bars or spikes).
 -rplottype- specifies the range plot type used to draw the confidence limits
  (defaulting to -rcap- to give capped CI plots).
 -nociforeground- specifies whether or not the confidence intervals
   are in the foreground (with the estimates in the background).
 -estopts- specifies the estimates options
  (to be passed to -graph twoway scatter- to control the estimate points)
 -ciopts- specifies the confidence interval options
  (to be passed to -graph twoway `rplottype' - to control the confidence limits).
 -addplot- specifies other plots to be added to the generated graph.
 -baddplot- specifies other plots to be added to the generated graph
  in the background.
 -plot- is an alternative way of specifying -addplot-.
 -nograph- specifies that the graph should not be drawn
  (useful for building a -twoway- command from -r()- elements).
 -by()- is the by-option for the graph
  (which is supplied with a default legend() suboption
  if this suboption is absent).
 -supby()- specifies that superimposed multiple CI plots
  are to be produced,
  one for each value of a single variable.
 -estoptsi- specifies estimates options
  for each superimposed multiple CI plot
  (to be added to -estopts()-).
 -cioptsi- specifies confidence interval options
  for each superimposed multiple CI plot
  (to be added to -ciopts()-)
*/

*
 Set -addplot()- option if given as -plot()- option
*;
if `"`addplot'"'=="" {;
  local addplot `"`plot'"';
};
else if `"`plot'"'!="" {;
  disp as error "Options addplot() and plot() are alternatives and cannot both be specified";
  error 498;
};

*
 Store extra -twoway- options in macro `twoptions-
*;
local twoptions `"`options'"';

*
 Check estimate and range plot types
 and -ciforeground- option
 and set to default if empty
 and unabbreviate if necessary
*;
if `"`eplottype'"'=="" {;local eplottype "scatter";};
if `"`rplottype'"'=="" {;local rplottype "rcap";};
if length(`"`eplottype'"')<2 {;
  disp as error "eplottype() must be specified by at least 2 letters";
  error 498;
};
local found=0;
foreach PT in scatter connected line area bar spike dropline {;
  if !`found' & strpos("`PT'",`"`eplottype'"')==1 {;
    local eplottype "`PT'";
    local found=1;
  };
};
if length(`"`rplottype'"')<2 {;
  disp as error "rplottype() must be specified by at least 2 letters";
  error 498;
};
local found=0;
foreach PT in  rarea rbar rspike rcap rcapsym rscatter rline rconnected {;
  if !`found' &  strpos("`PT'",`"`rplottype'"')==1 {;
    local rplottype "`PT'";
    local found=1;
  };
};
if "`ciforeground'"=="" {;
  * Rank for -eplottype()- *;
  if inlist("`eplottype'","scatter","connected","line") {;local eplotrank=1;};
  else if inlist("`eplottype'","dropline","spike") {;local eplotrank=2;};
  else if inlist("`eplottype'","bar") {;local eplotrank=3;};
  else if inlist("`eplottype","area") {;local eplotrank=4;};
  else {;local eplotrank=5;};
  * Rank for -rplottype()- *;
  if inlist("`rplottype'","rscatter","rconnected","rline") {;local rplotrank=1;};
  else if inlist("`rplottype'","rcapsym","rcap","rspike") {;local rplotrank=2;};
  else if inlist("`rplottype'","rbar") {;local rplotrank=3;};
  else if inlist("`rplottype","rarea") {;local rplotrank=4;};
  else {;local rplotrank=5;};
  * Compare ranks *;
  if `rplotrank'<=`eplotrank' {;
    local ciforeground "ciforeground";
  };
  else {;
    local ciforeground "nociforeground";
  };
};

*
 Variables to plot and their labels
*;
local estimate:word 1 of `varlist';
local clmin:word 2 of `varlist';
local clmax:word 3 of `varlist';
local parmid:word 4 of `varlist';
local estlab:var lab `estimate';
if `"`estlab'"'=="" {;local estlab "`estimate'";};
local parmlab:var lab `parmid';
if `"`parmlab'"'=="" {;local parmlab "`parmid'";};

*
 -if- and -in- qualifiers
*;
local ifin "";
if `"`if'"'!="" & `"`in'"'!="" {;
  local ifin `"`if' `in'"';
};
else if `"`if'"'!="" {;
  local ifin `"`if'"';
};
else if `"`in'"'!="" {;
  local ifin `"`in'"';
};

*
 Call _eclplot to generate subcommand list in macro -subcmd-
 and generate default legend in macro -deflegend-
*;
if `"`supby'"'=="" {;
  *
   Single estimate amd confidence limit plot
  *;
  _eclplot `estimate' `clmin' `clmax' `parmid', `horizontal' eplottype(`eplottype') rplottype(`rplottype') `ciforeground'
    estopts(pstyle(p1) `estopts') ciopts(pstyle(p2) `ciopts');
  local subcmd `"`r(subcmd)'"';
  local deflegend "legend(off)";
};
else {;
  *
   Superimposed multiple estimate and confidence limit plots
  *;
  *
   Parse -supby()- suboptions
  *;
  local 0 `"`supby'"';
  syntax varname [ , Missing Truncate(passthru) SPAceby(real 0) OFFset(real 0) ];
  local supbyvar "`varlist'";
  cap conf numeric var `supbyvar';
  local stringsupbyvar=_rc;
  *
   Create superimposed plot sequence variable
  *;
  tempvar supseqvar;
  tempname supseqlab;
  qui egen `supseqvar'=group(`supbyvar') `ifin', `missing' label lname(`supseqlab') `truncate';
  summ `supseqvar', meanonly;
  local nsupby=r(max);
  if missing(`nsupby') {;
    disp as error "No valid values for supby() variable: `supbyvar'";
    error 2000;
  };
  else if `nsupby'>`msupby' {;
    disp as error "Number of superimposed plots (`nsupby') greater than maximum of `msupby'";
    error 498;
  };
  *
   Create legend order and default legend
  *;
  local legord "";
  qui levelsof `supbyvar' `ifin', `missing' lo(supbylevs);
  local i1=0;
  foreach SBL in `supbylevs' {;
    local i1=`i1'+1;
    * Key sequence number for estimates plot *;
    local keyseq=2*`i1';
    if "`ciforeground'"=="ciforeground" {;
      local keyseq=`keyseq'-1;
    };
    if `stringsupbyvar' {;
      local keytext `"`SBL'"';
    };
    else {;
      local keytext: label (`supbyvar') `SBL';
    };
    * Add -keytext- to legend order if quotable in double quotes *;
    cap local junk1=`"`macval(keytext)'"';
    if _rc==0 {;
      local legord `"`legord' `keyseq' `"`keytext'"'"';
    };
    else {;
      local legord `"`legord' `keyseq' `""'"';
    };
  };
  local deflegend `"legend(order(`legord'))"';
  *
   Create parameter position variable
  *;
  if `offset'==0 & `spaceby'==0 {;
    local parmpos "`parmid'";
  };
  else {;
    tempvar parmpos;
    qui clonevar `parmpos'=`parmid' `ifin';
    qui replace `parmpos' = `parmpos' + `offset' + `spaceby'*(`supseqvar'-1) `ifin';
  };
  *
   Create subcommand list
  *;
  _parse comma estop0 estwt0 : estopts;
  _parse comma ciop0 ciwt0 : ciopts;
  forv i1=1(1)`nsupby' {;
    *
     Estimate and confidence limit style sequences
     (THIS SHOULD WORK IF -msupby- IS A POSITIVE ODD INTEGER)
    *;
    local cistyle=2*`i1';
    local eststyle=`cistyle'-1;
    local cistyle=mod(`cistyle'-1,`msupby')+1;
    local eststyle=mod(`eststyle'-1,`msupby')+1;
    * Create estimate and CI options and weights *;
    _parse comma estop estwt: estopts`i1';
    _parse comma ciop ciwt: ciopts`i1';
    if `"`estwt'"'=="" {;local estwt `"`estwt0'"';};
    if `"`ciwt'"'=="" {;local ciwt `"`ciwt0'"';};
    local estop `"pstyle(p`eststyle') `estop0' `estop'"';
    local ciop `"pstyle(p`cistyle') `ciop0' `ciop'"';
    * Call -_eclplot- and add subcommands *;
    _eclplot `estimate' `clmin' `clmax' `parmpos',
      `horizontal' eplottype(`eplottype') rplottype(`rplottype') `ciforeground'
      estopts(`estop' `estwt') ciopts(`ciop' `ciwt') supseqvar(`supseqvar') supseqnum(`i1');
    if `i1'==1 {;
      local subcmd `"`r(subcmd)'"';
    };
    else {;
      local subcmd `"`subcmd' || `r(subcmd)'"';
    };
  };
};

*
 Add -addplot()- or -plot()- options to subcommand list if necessary
*;
if `"`addplot'"'!="" {;
  local subcmd `"`subcmd' || `addplot'"';
};
else if `"`plot'"'!="" {;
  local subcmd `"`subcmd' || `plot'"';
};

*
 Add -baddplot()- option to subcommand list if necessary
*;
if `"`baddplot'"'!="" {;
  local subcmd `"`baddplot' || `subcmd'"';
};

*
 Modify by-option if present
*;
if `"`by'"'!="" {;
  cap noi _parseby `by';
  if _rc!=0 {;
    disp as error `"Invalid by-option: by( `by' )"';
    error 498;
  };
  local byvarlist `"`r(byvarlist)'"';
  local bylegend `"`r(bylegend)'"';
  local bysubopts `"`r(bysubopts)'"';
  if `"`bylegend'"'=="" {;
    if `"`supby'"'=="" {;
      local bylegend "legend(off)";
    };
    else {;
      local bylegend "legend(on)";
    };
  };
  local by `"by(`byvarlist', `bylegend' `bysubopts')"';
};

*
 Generate -twoway- options list
*;
if "`horizontal'"=="horizontal" {;
  local twowayopts `"yscale(reverse) ylabel(, valuelabel angle(0)) ytitle(`"`parmlab'"') xtitle(`"`estlab'"') `by' `deflegend' `twoptions'"';
};
else {;
  local twowayopts `" xlabel(, valuelabel) ytitle(`"`estlab'"') xtitle(`"`parmlab'"') `by' `deflegend' `twoptions'"';
};

* -twoway- command *;
local cmd `"twoway `subcmd' || `ifin' , `twowayopts'"';

*
 Generate plots
*;
if "`graph'"!="nograph" {;
  `cmd';
};

*
 Save command, subcommands and twoway options in -r()-
*;
return local cmd `"`cmd'"';
return local twowayopts `"`twowayopts'"';
return local ifin `"`ifin'"';
return local allplots `"`subcmd'"';
return local plot `"`plot'"';
return local baddplot `"`baddplot'"';
return local addplot `"`addplot'"';

end;

program _eclplot, rclass;
version 13.0;
/*
  Input features of estimates and confidence limits plots
  and output subcommand pair defining estimate and confidence limits plot
  in -r(subcmd)-
*/

syntax varlist(numeric min=4 max=4) [, HORizontal
  EPLOTtype(string) RPLOTtype(string) noCIForeground
  ESTOPTS(string asis) CIOPTS(string asis)
  SUPSEQVAR(varname numeric) SUPSEQNUM(integer 1)
  ];
/*
 -varlist- is a list of 4 variables
  (estimate, lower confidence limit, upper confidence limit,
  and numeric parameter ID variable to plot them against).
 -horizontal- specifies that a horizontal CI plot will be drawn
  (instead of the default vertical CI plot).
 -eplottype- specifies the plot typr used to draw the estimates
   (defaulting to -scatter- to give unconnected symbols
   and no areas, bars or spikes).
 -rplottype- specifies the range plot type used to draw the confidence limits
  (defaulting to -rcap- to give capped CI plots).
 -nociforeground- specifies whether or not the confidence intervals
   are in the foreground (with the estimates in the background).
 -estopts- specifies the estimates options
  (to be passed to -graph twoway scatter- to control the estimate points)
 -ciopts- specifies the confidence interval options
  (to be passed to -graph twoway `rplottype' - to control the confidence limits).
 -supseqvar- specifies a variable containing sequence order
  for superimposed multiple plots.
 -supseqnum- specifies a sequence order for the current superimposed plot.
*/

* Variables to plot *;
local estimate:word 1 of `varlist';
local clmin:word 2 of `varlist';
local clmax:word 3 of `varlist';
local parmid:word 4 of `varlist';

*
 Specify current superimposed plot
*;

if "`supseqvar'"=="" {;
  local andsup "";
};
else {;
  local andsup " & `supseqvar'==`supseqnum'";
};

*
 Generate estimate plot subcommand
*;
if inlist(`"`eplottype'"',"area","bar","spike","dropline") {;
  * Orientation decided by "horizontal" and "vertical" options *;
  if "`horizontal'"=="horizontal" {;
    local eplotcmd `"`eplottype' `estimate' `parmid' if  !missing(`estimate') & !missing(`parmid')`andsup' , horizontal `estopts'"';
  };
  else {;
    local eplotcmd `"`eplottype' `estimate' `parmid' if  !missing(`estimate') & !missing(`parmid')`andsup' , vertical `estopts'"';
  };
};
else if inlist(`"`eplottype'"',"scatter","connected","line") {;
  * Orientation decided by variable order *;
  if "`horizontal'"=="horizontal" {;
    local eplotcmd `"`eplottype' `parmid' `estimate'  if  !missing(`estimate') & !missing(`parmid')`andsup' , `estopts'"';
  };
  else {;
    local eplotcmd `"`eplottype' `estimate' `parmid'  if  !missing(`estimate') & !missing(`parmid')`andsup' , `estopts'"';
  };
};
else {;
  * Impossible to decide how to decide orientation *;
  disp as error `"Unknown estimate plot type - `eplottype'"';
  error 498;
};

*
 Generate confidence limit plot subcommand
*;
if "`horizontal'"=="horizontal" {;
  local rplotcmd `"`rplottype' `clmax' `clmin' `parmid' if  !missing(`clmin') & !missing(`clmax') & !missing(`parmid')`andsup' , horizontal `ciopts'"';
};
else {;
  local rplotcmd `"`rplottype' `clmax' `clmin' `parmid' if  !missing(`clmin') & !missing(`clmax') & !missing(`parmid')`andsup' , vertical `ciopts'"';
};

*
 Combine 2 subcommands to form subcommand list
*;
if "`ciforeground'"=="nociforeground" {;
  * Confidence limits in background *;
  local subcmd `"`rplotcmd' || `eplotcmd'"';
};
else {;
  * Confidence limits in foreground *;
  local subcmd `"`eplotcmd' || `rplotcmd'"';;
};

*
 Return subcommand pair
*;
return local subcmd `"`subcmd'"';

end;

prog def _parseby, rclass;
version 13.0;
/*
  Parse a by-option
  returning its varlist in r(byarlist), its legend() suboption in r(bylegend),
  and its other suboption in r(bysubopts).
*/

syntax varlist [, LEGend(passthru) * ];
/*
 -varlist- is the list of by-variables.
 -legend()- is the legend suboption of the by-option.
*/

return local byvarlist `"`varlist'"';
return local bylegend `"`legend'"';
return local bysubopts `"`options'"';

end;