/*
tree_plot.ado  -- 2023Sep20, oct6, 17, 18; David Kantor.

NOT TO BE confused with treeplot.ado -- a separate user-generated program, from SSC.

This is an adjunct to tree.ado; provides the plotting capability.

Originally, this was intended to be temporary, to be added to tree.ado.
Later (2023oct05 and later), it was decided to keep it as a separate file.
*/

program tree_plot

* version 1.0.0 2023oct18
version 14



local prob_places_default "3"


syntax [varlist(default=none)], [superlabel(string) numbers GPHSAVing(string) gphasis gphreplace ///
	dosave(string) doreplace verbose prob_places(integer `prob_places_default') ///
	noprob]

global tree_superlabel `"`superlabel'"'
global tree_numbers "`numbers'"
global tree_noprob "`prob'"

if `prob_places' <0 {
	disp as err "prob_places must be non-negative"
	exit 198
}

local prob_places_tot = `prob_places' + 2
global tree_prob_fmt "%`prob_places_tot'.`prob_places'g"


if "`varlist'" ~= "" {
	local payoffvars `: char _dta[payoffvars]'
	if ~`: list varlist in payoffvars' {
		disp as err "varlist must be subset of payoffvars"
		exit 198
	}
	/*
	Form the set of vars to display in a plot.
	User has input the base names; we interpret them to mean the R_ variables.
	*/
	foreach v of local varlist {
		local varstodisp "`varstodisp' R_`v'"
	}
	global tree_varstodisp "`varstodisp'"
}
else {
	global tree_varstodisp
}




/* Stuff that should go into tree.ado, in tree_init,
minus the -capture-. This is fo if we ever meld tree_plot into tree.ado.
*/
capture gen float __ypos = .
/* END Stuff that should go into tree.ado, in tree_init */



/* Determine the __ypos values. */
global tree_maxdepth -1 /* start below 0; first actual depth is 0 */
tree_traverse, depth(0) id(1) parent(0) action1(set_ypos_term) action4(set_ypos_int) `verbose'

summ __ypos, meanonly
global tree_triangle_half_height = (r(max) - r(min))/(2*44)
/* -- r(min) ought to be 0, but we code for the general case. */

global triangle_width = $tree_maxdepth * 16 / 70
global marker_xsep = $triangle_width/2
/* marker_xsep: separation in the x direction to prevent lines from penetrating the circles. */


if `"`dosave'"' ~= "" {
	detect_file_ext, fname(`"`dosave'"')
	if ~`s(ext_detected)' {
		disp "appending .do to dosave name"
		local dosave `"`dosave'.do"'
	}
	local dofilename `"`dosave'"'
}
else {
	tempfile dofilename
}
if "`doreplace'" ~= "" {
	local doreplace "replace"
}

tempname gfile
file open `gfile' using `"`dofilename'"', write text `doreplace'
/* We could add an option for the -all- option to -file open-. */


file write `gfile' `"/* `dofilename'"' _n
file write `gfile' "Created by tree.ado, $S_DATE $S_TIME" _n
file write `gfile' "*/" _n
file write `gfile' "#delimit ;" _n _n

/* Set gfile as a global as well, to communicate with the procedures that
write to it via tree_traverse. The alternative is to add an option to all the
action routines. This is less disruptive.
*/
global tree_gfile "`gfile'"

file write `gfile' "twoway" _n


tree_traverse, depth(0) id(1) parent(0) action1(plot_node) `verbose'

file write `gfile' ", yscale(reverse off) xscale(off) legend(off)"

if `"`gphsaving'"' ~= "" {
	file write `gfile' `" saving(`"`gphsaving'"',"'
	if "`gphasis'" ~= "" {
		file write `gfile' " asis"
	}
	if "`gphreplace'" ~= "" {
		file write `gfile' " replace"
	}
	file write `gfile' ")" _n
}

file write `gfile' ";" _n

file close `gfile'


disp "doing the file... "

do `"`dofilename'"'

end /* tree_plot */



prog def plot_node
/* An action1 routine. */
syntax, id(integer) parent(integer) depth(integer) [location(string) verbose]
/* The filehandle is communicated via a global: tree_gfile. */


/* The l1 and r1 variants of xpos* are for preventing the lines from penetrating the circles. */

local horiz_len "12"
local superroot_horiz_len "5"

local xpos_parent =(`depth'-1) * 16 +4
local xpos_parent_r1 = `xpos_parent' + $marker_xsep



local xpos = `depth' * 16 +4
local xpos_l1 = `xpos' - $marker_xsep
local xpos_l2 = `xpos' - `horiz_len'  /* for the bend between the angled and horizontal line-segments */
local xpos_l3 = `xpos' -`superroot_horiz_len'  /* for the line before the superroot */

local ypos = __ypos[`id']
local ypos_parent = __ypos[`parent']

local state_vec_gap "0"

if `parent' >0 {
	#delimit ;
	file write $tree_gfile " pci `ypos_parent' `xpos_parent_r1' `ypos' `xpos_l2'";  /* angled line */
	file write $tree_gfile "	`ypos' `xpos_l2' `ypos' `xpos_l1', lcolor(black) ||" /* horizontal line */
	_n;
	#delimit cr
	/* Supposedly, you can put "label text" into the pci, but I find that it doesn't work.
	So do the label separately:

	*/
	if __nodetype[`id'] == "terminal":nodetype {
		left_facing_triangle, ypos(`ypos') xpos(`xpos') half_height($tree_triangle_half_height) width($triangle_width) xsep($marker_xsep)
		local msym "i" /* invisible; for the next scatteri. */
		local state_vec_pos "3" /* to the right */
		local state_vec_gap = 1-$marker_xsep
		/* ~~~ That 1 should really be a width parameter; same width as in option to left_facing_triangle. */

	}
	else {
		local msym "O" /* circle; for the next scatteri. */
		local state_vec_pos "8" /* to the left, below */
	}
	if __nodetype[`id'] == "root":nodetype {
		/* This should line up with depth ==1. */
		local fillcolor "ltblue"
	}
	else {
		local fillcolor "yellow"
	}

	/* Next: scatteri is for the nodes.
	Also put in the nodename ~~~or label~~~.
	*/
	/* Label the node with text (node name ~~~or the node label), plus (prob in parens).
	*/
	if "$tree_noprob" == "" {
		if mi(R_prob[`id']) {
			local probtext: disp %1.0g R_prob[`id']
		}
		else {
			local probtext: disp $tree_prob_fmt R_prob[`id']
		}
		local probtext " (`probtext')"
	}
	local nodelabel =__nodename[`id']
}
else {
	/* `parent' = 0 -- i.e., no parent; this should occur only for the superroot,
	that is, __nodetype[`id'] == "superroot":nodetype.
	*/
	file write $tree_gfile " pci `ypos' `xpos_l3' `ypos' `xpos_l1', lcolor(black) ||" _n

	local superlabel "`=__nodename[`id']'"
	if "$tree_superlabel" ~= "" {
		local superlabel "$tree_superlabel"
	}

	local nodelabel "`superlabel'"
	local msym "S" /* Square */
	local fillcolor "pink"
}


file write $tree_gfile `" scatteri `ypos' `xpos' "`nodelabel'`probtext'", mlabposition(10)  mlabsize(vsmall) mlabcolor(black) msym(`msym') mcolor(black) mfcolor(`fillcolor') msize(medium) mlabgap(0) ||"' _n


/* The state vector */
if "$tree_varstodisp" ~= "" {
	foreach v of global tree_varstodisp {
		local vars_disp_text "`vars_disp_text' `:disp `v'[`id']'"
	}
	file write $tree_gfile `" scatteri `ypos' `xpos' "`vars_disp_text'", mlabposition(`state_vec_pos') mlabsize(vsmall) mlabcolor(black) msym(i) mlabgap(`state_vec_gap') ||"' _n
}

/* Node numbers */
if "$tree_numbers" ~= "" {
	/* display the node number */
	file write $tree_gfile `" scatteri `ypos' `xpos' "`id'", mlabposition(6) mlabgap(0) ms(i) ||"' _n
}

end /* plot_node */



prog def left_facing_triangle
syntax, ypos(real) xpos(real) half_height(real) width(real) xsep(real)

local xpos_l1 = `xpos' - `xsep'
local xpos_r1 = `xpos' + `width' - `xsep'
local ypos_plus = `ypos'+ `half_height'
local ypos_minu = `ypos'- `half_height'

#delimit ;
file write $tree_gfile " pci
`ypos' `xpos_l1'
`ypos_plus' `xpos_r1'

`ypos_plus' `xpos_r1'
`ypos_minu' `xpos_r1'

`ypos_minu' `xpos_r1'
`ypos' `xpos_l1', lcolor(black) ||"
_n;
#delimit cr

end /* left_facing_triangle */





prog set_ypos_term
/* An action1 routine.
Set __ypos of a given TERMINAL node only.
Also set a few other items (globals).
*/
syntax, id(integer) parent(integer) depth(integer) [location(string) verbose]
if `depth' > $tree_maxdepth {
	/* First time visiting this depth */
	global tree_maxdepth `depth'
	global tree_ypos_`depth' /* clear */
}

if __nodetype[`id'] == "terminal":nodetype {

	if "${tree_ypos_`depth'}" == "" {
		local next_avail_ypos 0
	}
	else {
		local next_avail_ypos = ${tree_ypos_`depth'} + 16
	}
	replace_ypos = `next_avail_ypos' in `id' , depth(`depth') `verbose'
	if "`verbose'" ~= "" {
		disp "set_ypos_term; node `id', dep `depth', __ypos " __ypos[`id']
	}
}

end /* set_ypos_term */



prog set_ypos_int
/* An action3 routine.
Set __ypos of a given INTERIOR node (or root or superroot) only.
Also set a few other items (globals).
*/
syntax, id(integer) parent(integer) depth(integer) [location(string) verbose]
if `depth' > $tree_maxdepth {  /* shouldn't happen */
	global tree_maxdepth `depth'
	global tree_ypos_`depth' /* clear */
}

if __nodetype[`id'] < "terminal":nodetype {

	if "${tree_ypos_`depth'}" == "" {
		local next_avail_ypos 0
	}
	else {
		local next_avail_ypos = ${tree_ypos_`depth'} + 16
	}

	local mean_ypos_of_children = ///
	 (__ypos[__branch_head[`id']] + __ypos[__branch_tail[`id']])/2
	/*~~~*/ /* disp "set_ypos_int; mean_ypos_of_children `mean_ypos_of_children'" */
	if mi(`mean_ypos_of_children') {
		/* There are no children; an improper condition, but can happen easily. */
		local mean_ypos_of_children "`next_avail_ypos'"
	}

	replace_ypos = `mean_ypos_of_children' in `id', depth(`depth')

	if "`verbose'" ~= "" {
		disp "set_ypos_int; node `id', dep `depth', __ypos " __ypos[`id']
		disp " ---- next_avail_ypos `next_avail_ypos'"
	}
	if `mean_ypos_of_children' < `next_avail_ypos' {
		global tree_ypos_increment = `next_avail_ypos' - `mean_ypos_of_children'
		/* Increment that node's ypos, as well as all its descendants -- the subtree
		rooted at `id'.

		This shall have the effect of making __ypos[`id'] = `next_avail_ypos'.
		Note, too, that this is one tree-traversal routine that invokes
		another tree-traversal routine.
		*/
		if "`verbose'" ~= "" {
			disp " -- incrementing subtree by $tree_ypos_increment"
		}
		tree_traverse, depth(`depth') id(`id') parent(`parent') action1(increment_ypos) `verbose'
		/* increment_ypos will take care of adjusting tree_ypos_`depth' in the depth of the
		present node and all decendant nodes.
		*/
	}
}


end /* set_ypos_int */


prog def replace_ypos
syntax =/exp in, depth(integer) [verbose]
/* replace __ypos at a specified obsno. But also maintain tree_ypos_`depth'.
The in-use syntax will resemble -replace __ypos=...- but is different in that
a depth option is required.

The -in- feature could take a range, but it is intended to take a single value.
*/

tempname newvalue
scalar `newvalue' = `exp'


if "`verbose'" ~= "" {
	disp "replace_ypos = `=`newvalue'' `in' " _cont
}
else {
	local qui "qui"
}

`qui' replace __ypos =`newvalue' `in'
/* -- if not qui, then the report from the -replace- will end the disp line. */


if "${tree_ypos_`depth'}" == "" {
	global tree_ypos_`depth' =`newvalue'
	if "`verbose'" ~= "" {
		disp " initialized tree_ypos_`depth'"
	}
}
else if ${tree_ypos_`depth'} < `newvalue' {
	global tree_ypos_`depth' =`newvalue'
	if "`verbose'" ~= "" {
		disp " reset tree_ypos_`depth'"
	}
}
/* Based on how this program is used, we would proabably activate one of those paths
that initialize or reset tree_ypos_`depth'. I.E., if you put an -else- here,
it would not be reached.
*/

end /* replace_ypos */



prog def increment_ypos
/* An action1 routine. */
syntax, id(integer) parent(integer) depth(integer) [location(string) verbose]

replace_ypos = __ypos[`id'] + $tree_ypos_increment in `id', depth(`depth')
end /* increment_ypos */




prog def detect_file_ext, sclass
/* Determine whether a filename has an extension.
The criterion is that there is a . in the name -- not including the directory
spec if present.
*/
syntax, fname(string)

local fname = subinstr(`"`fname'"', "\", "/", .)
local fnamerev = strreverse(`"`fname'"')

local p1 = strpos(`"`fnamerev'"', "/")
local p2 = strpos(`"`fnamerev'"', ".")

assert ~mi(`p1')
assert ~mi(`p2')

sreturn local ext_detected = `p2' & (~`p1' | (`p2' < `p1'))

end /* detect_file_ext */



prog def tree_traverse
/* Regretably, it is necessary to have a copy of this in both tree.ado and
tree_plot.ado.  -- Unless we meld tree_plot into tree.ado.
*/

/* This shall do recursive calling! */
syntax, id(integer) parent(integer) depth(integer) ///
	[location(string) action1(name) action4(name) VERbose]

/*
id: the obs no of the node that is visited; all of the children of this node are
then visited -- recursively.

Presumably, when this is first called, id is the root of the tree being traversed.

action1,4 program names -- to be called at various points.

Prior to 2023may05, we had action2 & 3; eliminating them.
Previous Action3 rotines will be blended into action1 routines, but reverse the parent-child roles.
See tree.ado_save021 for historical reference.

action1 is what is to be DONE at each node as it is visited, prior to
traversing the descendant nodes.

[action3 is done immediately after traversing a child node, for passing info from
child back to the present node.]

action4 is done after traversing all child nodes.
(action4 added 2022mar01.)
Keep the name action4 as historical legacy.

These are the options that action routines must take:
action1: id, parent, depth, location
action4: id, parent, depth, location

All take optional verbose; added 2022oct27.
In action1, parent was added 2022feb28.
[child, for action3, is locally generated -- not passed in.]

Adding a probno option; 2022feb28, 21:57. Removed, 2022jun27.


Each of these is expected to have the options shown above.
BUT because of the generality, an instance of a given species of action routine
must take all the options, but might not use them all.

Similarly, not every call to tree_traverse needs all the options.
*/

if _N <1 {
	disp "(no tree)"
	exit 0
}

/*~~*/  /*disp "tree_traverse; id `id', depth `depth'" */

local location "`location' `=__nodename[`id']'"
/* Note that location initially comes in as the parent's location, but
it now pertains to the present node.
*/

/*~~debug: disp "tree_traverse; location `location'" */


if "`action1'" ~= "" {
	`action1', id(`id') parent(`parent') depth(`depth') location(`location') `verbose'
}

local jj = __branch_head[`id']

/* action2 would go here, conditionally; but never needed it. */

while ~mi(`jj') {
	/* Recursive call: */
	tree_traverse, id(`jj') parent(`id') depth(`=`depth'+1') ///
		location(`location') ///
		action1(`action1') action4(`action4') `verbose'
	local jj = __next[`jj']
}

if "`action4'" ~= "" {
	`action4', id(`id') parent(`parent') depth(`depth') location(`location') `verbose'
}
end /* tree_traverse */