*! streamplot v1.61 (15 Jan 2024)
*! Asjad Naqvi (asjadnaqvi@gmail.com)

* v1.61 (15 Jan 2024): fixed wrong locals. changed ylab to just lab.
* v1.6  (15 Oct 2023): cat() option added. yrev, labcond() fixed. major code cleanup.
* v1.52 (25 Aug 2023): Support for aspect(), saving(), nolabel, nodraw, xscale() and graphregion() added.
* v1.51 (28 May 2023): Clean up labcond and offset changes to percentages.
* v1.5  (20 Nov 2022): recenter option added. improved variable precision.
* v1.4  (08 Nov 2022): Major code cleanup and some parts reworked. Observation checks. Palette options. label controls.
* v1.3  (20 Jun 2022): Add marker labels and format options
* v1.2  (14 Jun 2022): passthru optimizations. error checks. reduce the default smoothing. labels fix
* v1.1  (08 Apr 2022)
* v1.0  (06 Aug 2021)


cap program drop streamplot

program streamplot, sortpreserve

version 15

	syntax varlist(min=2 max=2 numeric) [if] [in], by(varname)  ///
		[ palette(string) alpha(real 100) smooth(real 3) ] ///
		[ LColor(string)  LWidth(string) labcond(real 0) ] 		///
		[ LABSize(string) LABColor(string) offset(real 15)    ] ///
		[ xlabel(passthru) xtitle(passthru) title(passthru) subtitle(passthru) note(passthru) scheme(passthru) name(passthru) xsize(passthru) ysize(passthru)  ] ///
		[ PERCENT FORMAT(string) RECenter(string) ]  ///
		[ aspect(passthru) saving(passthru) NOLABel nodraw xscale(passthru) graphregion(passthru) ]  /// v1.5x
		[ cat(varname) YREVerse  ]   // v1.6



	// check dependencies
	capture findfile colorpalette.ado
	if _rc != 0 {
		display as error "colorpalette package is missing. Install the {stata ssc install colorpalette, replace:colorpalette} and {stata ssc install colrspace, replace:colrspace} packages."
		exit
	}

	capture findfile labmask.ado
	if _rc != 0 {
		qui ssc install labutil, replace
	}

	marksample touse, strok
	gettoken yvar xvar : varlist

	if "`cat'"!= "" {
		qui levelsof `cat'
		if r(r) > 2 {
			di as error "`cat' in cat() is not a binary variable."
			exit 198
		}
	}

	if `"`format'"' == "" local format "%12.0fc"



qui {
preserve

	keep if `touse'



	if "`cat'"=="" {
		gen _cat = 1  // run a dummy
		local cat _cat
		local rebasecat 0
	}
	else {
		local rebasecat 1
	}

	collapse (sum) `yvar', by(`xvar' `by' `cat')



	gen ones = 1
	bysort `by': egen counts = sum(ones)
	egen tag = tag(`by')
	summ counts, meanonly

	if r(min) < 10 {
		if "`droplow'" == "" {
			count if counts < 10 & tag==1
			di as error "Groups with errors:"
			noi list `by' if counts < 10 & tag==1
			di as error "`r(N)' group(s) (`by') have fewer than 10 observations which is insufficient to use {stata help streamplot:streamplot}."
			exit
		}
		else {
			drop if counts < 10
		}
	}

	count
	if r(N) == 0 {
		di as error "No groups fulfill the criteria for {stata help streamplot:streamplot}."
		exit
	}

	drop ones tag counts

		fillin  `by' `xvar'
	recode `yvar' (.=0)
	sort `xvar' `by'



	// pass on cat variable to added observations

	bysort `by': replace `cat' = `cat'[_N] if `cat'==.
	cap drop _fillin

	egen _order = group(`cat' `by')  // this is the primary order category



	cap confirm numeric var `by'
		if _rc!=0 {        // if numeric, make sure its numeric is ordered from 1
			tempvar tempov
			encode `by', gen(`tempov')
			labmask _order, val(`by')
		}
		else {  // if string


			if "`: value label `by''" != "" {
				tempvar tempov
				decode `by', gen(`tempov')
				labmask _order, val(`tempov')
			}
			else {
				labmask _order, val(`by')

			}
		}

	local by _order



	if "`yreverse'" != "" {

		clonevar over2 = `by'

		summ `by', meanonly
		replace `by' = r(max) - `by' + 1

		if "`: value label over2'" != "" {
			tempvar group2
			decode over2, gen(`group2')
			replace `group2' = string(over2) if `group2'==""
			labmask `by', val(`group2')
		}
		else {
			labmask `by', val(over2)
		}
	}


	keep  `xvar' `cat' `by' `yvar'
	order `xvar' `cat' `by' `yvar'

	xtset `by' `xvar'


	replace `yvar' = 0 if `yvar' < 0	 // this is technically wrong but we do it anyways   // fix in later versions
	tssmooth ma _ma  = `yvar' , w(`smooth' 1 0)

	// add the range variable on the x-axis
	summ `xvar' if _ma != ., meanonly

	if "`nolabel'"!="" local offset 0  // reset to 0

	local xrmin = r(min)
	local xrmax = r(max) + ((r(max) - r(min)) * (`offset' / 100))


	sort `xvar' `by' // extremely important for stack order.
	by `xvar': gen double _stack = sum(_ma)


	** preserve the labels for use later

	local mylab: value label `by'

	levelsof `by', local(idlabels)      // store the id levels

	foreach x of local idlabels {
		local idlab_`x' : label `mylab' `x'  // store the corresponding value label in a macro

	}



	if "`yreverse'" == "" {
		summ `cat'
		summ _order if `cat'==r(max)
		local rebase = r(min) - 1   // if cat is defined, store the rebase index
	}
	else {
		summ `cat'
		summ _order if `cat'==r(max)
		local rebase = r(max)
	}


	ren `yvar' _yvar
	drop `cat'



	reshape wide _stack _yvar _ma  , i(`xvar') j(`by')

	foreach x of local idlabels {        // here we know how many variables we have

		lab var _stack`x' "`idlab_`x''"
		lab var _ma`x'	  "`idlab_`x''"
		lab var _yvar`x'  "`idlab_`x''"
	}



	gen _stack0 = 0  // we need this for area graphs

	order `xvar' _stack0


	if `rebasecat' == 0 {
		ds _ma*
		local items : word count `r(varlist)'
		local items = `items' - 1

		if "`recenter'" == "" | "`recenter'"=="middle"  | "`recenter'"=="mid"  | "`recenter'"=="m" {
			gen double _meanval  =  _stack`items' / 2
		}

		if "`recenter'" == "bottom" | "`recenter'"=="bot" | "`recenter'"=="b" {
			gen _meanval  =  0
		}

		if "`recenter'" == "top" | "`recenter'"=="t"  {
			local items2 = `items' + 1
			gen double _meanval  =   _stack`items2'
		}
	}
	else {
		gen double _meanval = _stack`rebase'
	}



	foreach x of varlist _stack* {
		gen double `x'_norm  = `x' - _meanval
	}

	drop _meanval


	// this part is for the mid points

	summ `xvar'
	gen last = 1 if `xvar'==r(max)


	ds _stack*_norm
	local items : word count `r(varlist)'
	local items = `items' - 2



	forval i = 0/`items' {
		local i0 = `i'
		local i1 = `i' + 1

		gen double _ylab`i1'  = (_stack`i0'_norm + _stack`i1'_norm) / 2 if last==1
	}



	egen double _lastsum  = rowtotal(_ma*)  if last==1


	foreach x of varlist _ma* {
		gen double `x'_share = (`x' / _lastsum) * 100
		}


	drop _lastsum


**** automate this part


	ds _stack*norm
	local items : word count `r(varlist)'
	local items = `items' - 1

	foreach x of numlist 1/`items' {

		   if `"`percent'"' != "" {
			local ylabvalues `"string(_ma`x'_share, `"`format'"') + "%""'

			local labvar _ma`x'_share

		   }
		   else {
			local ylabvalues `"string(_yvar`x', `"`format'"')"'

			local labvar _yvar`x'
		   }



		local t : var lab _ma`x'
		gen _label`x'  = "`t'" + " (" + `ylabvalues' + ")" if last==1 & `labvar' >= `labcond'

	}




	if "`labsize'"  == "" 			local labsize 1.6
	if "`lcolor'"    == "" 			local lcolor white
	if "`lwidth'"    == "" 			local lwidth  0.05
	if "`labcolor'" != "palette" 	local ycolor  `labcolor'
	if "`palette'" == "" {
		local palette tableau
	}
	else {
		tokenize "`palette'", p(",")
		local palette  `1'
		local poptions `3'
	}



summ _stack0_norm, meanonly

	if "`recenter'" == "" | "`recenter'" == "middle" | "`recenter'" == "mid"  | "`recenter'" == "m" {

		local ymin = -1 * abs(r(min)) * 1.05
		local ymax =      abs(r(min)) * 1.05
	}

	if "`recenter'" == "bottom" | "`recenter'" == "bot"  | "`recenter'" == "b" {

		local ymin = 0
		local ymax = abs(r(min)) * 1.05
	}

	if "`recenter'" == "top" | "`recenter'" == "t"  {

		local ymin = -1 * abs(r(min)) * 1.05
		local ymax =      0
	}


	if "`cat'" != "" {
		summ _stack0_norm, meanonly
		local ymin = -1 * abs(r(min)) * 1.05

		summ _stack`items'_norm, meanonly
		local ymax =  1 * abs(r(max)) * 1.05
	}


	ds _stack*norm
	local items : word count `r(varlist)'
	local items = `items' - 2



	forval x = 0/`items' {

	local numcolor = `items' + 1

	colorpalette `palette', n(`numcolor') nograph `poptions'

		local x0 =  `x'
		local x1 =  `x' + 1

		local areagraph `areagraph' rarea _stack`x0'_norm _stack`x1'_norm `xvar', fcolor("`r(p`x1')'") fi(100) lcolor(`lcolor') lwidth(`lwidth') ||


		if "`labcolor'" == "palette" {
			local ycolor  "`r(p`x1')'"
		}


		if "`nolabel'"=="" {
			local labels  `labels'  (scatter _ylab`x1' `xvar' if last==1, mlabel(_label`x1') mcolor(none) mlabsize(`labsize') mlabcolor("`ycolor'")) ||

		}

	}


	twoway ///
		`areagraph' ///
		`labels'	///
			, ///
				legend(off) ///
				yscale(noline) ///
				ytitle("") `xtitle'  ///
				ylabel(`ymin' `ymax', nolabels noticks nogrid) ///
				`xlabel' xscale(noline range(`xrmin' `xrmax'))   ///
				`title' `subtitle' `note' `scheme' `xsize' `ysize' `name' `aspect' `saving' `nodraw' `xscale' `graphregion'


	*/
restore
}

end



*********************************
******** END OF PROGRAM *********
*********************************