*! version 2.1 August 5, 2020 @ 14:22:08 UK
*! Sequence-Index-Plot

* Version 1.1: Original SJ contribution
* Version 1.2: Some new default settings  (proposed by m.kaulisch@utwente.nl)
*              New option subsequence
* Version 1.3: tempfiles with compount double quotes
* Version 1.4: Option -order()- allows varlist
* Version 1.5: Option -order()- uses automatic order at lower levels
* Version 1.6: Option -by(var, options)- gives an error when used with option -> fixed
* Version 1.7: Option se issued an error when used with by -> fixed
* Version 1.8: Impements Halpins propsals to fight against over-plotting
* Version 1.9: Option rbar implemented
* Version 1.9a: color(string asis)
* Version 2: Wrong scaling if minimum time > 1 -> fixed
* Version 2.1: color() with cmyk, rgb wired -> fixed

program define sqindexplot
version 9
	syntax [if] [in] ///
	  [, ranks(numlist) so se order(varlist) by(string)  ///
	  gapinclude SUBSEQuence(string)  ///
      color(string asis) xtitle(string asis) yscale(string) 	///
	  xsize(passthru) ysize(passthru) overplot(real 60) rbar * ]

	// Sq-Data
	if `"`_dta[SQis]'"' == `""' {
		di as error "data not declared as SQ-data; use -sqset-"
		exit 9
	}

	// if/in
	if `"`if'"' != `""' {
		tokenize `"`if'"', parse(" =+-*/^~!|&<>(),.")
		while `"`1'"' != `""' {
			capture confirm variable `1'
			if !_rc {
				local iflist  "`iflist' `1'"
			}
			macro shift
		}
	}
	if `"`iflist'"' != `""' CheckConstant `iflist', stop

	marksample touse
	if "`subsequence'" != "" quietly replace `touse' = 0 if !inrange(`_dta[SQtis]',`subsequence')

	// by
	if `"`by'"' != `""' {
		gettoken byvars byopts: by, parse(",")
	}

	// Set Device
	local device = cond("`rbar'"=="","rspike","rbar")

	preserve

	quietly {

		// Drop Sequences with Gaps
		if `"`gapinclude'"' == `""' {
			tempvar lcensor rcensor gap
			by `_dta[SQiis]' (`_dta[SQtis]'), sort: gen `lcensor' = sum(!mi(`_dta[SQis]'))
			by `_dta[SQiis]' (`_dta[SQtis]'): gen `rcensor' = sum(mi(`_dta[SQis]'))
			by `_dta[SQiis]' (`_dta[SQtis]'): ///
			  replace `rcensor' = ((_N-_n) == (`rcensor'[_N]-`rcensor'[_n])) & mi(`_dta[SQis]')
			by `_dta[SQiis]' (`_dta[SQtis]'): ///
			gen `gap' = sum(mi(`_dta[SQis]') & `lcensor' & !`rcensor')
			by `_dta[SQiis]' (`_dta[SQtis]'): ///
			replace `touse' = 0 if `gap'[_N]>0
		}
		keep if `touse'
		if _N == 0 {
			noi di as text "(No observations)"
			exit
		}

		tempvar episode begin end ordervar pointer

		// Keep Order only
		if "`so'" == "so" {
			tempvar torder stepwidth
			by `_dta[SQiis]' (`_dta[SQtis]'), sort: ///
			  keep if `_dta[SQis]' ~= `_dta[SQis]'[_n-1]
			by `_dta[SQiis]' (`_dta[SQtis]'), sort: replace `_dta[SQtis]' = _n
		}

		// Construct Scale for option SE
		else if	"`se'" == "se" {
			by `_dta[SQiis]' `_dta[SQis]', sort: keep if _n == 1
			fillin `_dta[SQiis]' `_dta[SQis]'
			by `_dta[SQiis]' (`_dta[SQis]'), sort: replace `_dta[SQtis]' = _n
			*replace  `_dta[SQis]' = . if _fillin
			drop if _fillin
		}

		// Option ranks (Remember: different than in other programs)
		if "`ranks'" != "" {
			tempfile original wide frequency
			tempvar n
			save `"`original'"'
			keep `_dta[SQiis]' `_dta[SQtis]' `_dta[SQis]'
			reshape wide `_dta[SQis]', i(`_dta[SQiis]') j(`_dta[SQtis]')
			save `"`wide'"'
			bysort `_dta[SQis]'*: gen `n' = _N
			bysort `_dta[SQis]'*: keep if _n==1
			KeepRanks `n', ranks(`ranks')
			sort `_dta[SQis]'*
			save `"`frequency'"'
			use `"`wide'"', clear
			sort `_dta[SQis]'*
			merge `_dta[SQis]'* using `"`frequency'"'
			keep if  _merge==3
			keep `_dta[SQiis]'
			sort `_dta[SQiis]'
			save `"`wide'"', replace
			use `"`original'"'
			sort `_dta[SQiis]'
			merge `_dta[SQiis]' using `"`wide'"'
			keep if _merge==3
			drop _merge
		}

		// Sort-Order
		tempfile original sorted
		save `"`original'"'
		keep `_dta[SQis]' `_dta[SQtis]' `_dta[SQiis]' `order' `byvars'
		reshape wide `_dta[SQis]', j(`_dta[SQtis]') i(`_dta[SQiis]')
		if "`byvars'"=="" {
			sort `order' `_dta[SQis]'* `_dta[SQiis]'
			gen long `ordervar' = _n
		}
		else by `byvars' (`order' `_dta[SQis]'* `_dta[SQiis]'), sort: gen long `ordervar' = _n
		keep `ordervar' `_dta[SQiis]'
		sort `_dta[SQiis]'
		save `"`sorted'"'
		use `"`original'"', clear
		sort `_dta[SQiis]'
		merge `_dta[SQiis]' using `"`sorted'"'

		// Number episodes
		by `_dta[SQiis]' (`_dta[SQtis]'), sort: gen `episode' = 1 ///
		  if `_dta[SQis]' ~= `_dta[SQis]'[_n-1]
		by `_dta[SQiis]' (`_dta[SQtis]'): replace `episode' = sum(`episode')

		// Keep 1st and last observation of each `Episode'
		by `_dta[SQiis]' `episode'  (`_dta[SQtis]'), sort: keep if _n==1 | _n==_N
		// Expand if 1st and last is the same
		by `_dta[SQiis]' `episode'  (`_dta[SQtis]'): gen byte `pointer' = _N==1
		expand 2 if `pointer'

		// generate the  time of `begin' and `end' of `episode'
		by `_dta[SQiis]' `episode' (`_dta[SQtis]'), sort: gen `begin' = `_dta[SQtis]'[1] -.5
		by `_dta[SQiis]' `episode' (`_dta[SQtis]'), sort: gen `end' = `_dta[SQtis]'[2] + .5

		// Generate ghost-variables (for legend)
		sum `_dta[SQtis]', meanonly
		tempvar legend1 legend2
		gen byte `legend1'=r(min)
		gen byte `legend2'=r(min)

		// Check graph size
		scatter `_dta[SQis]' `_dta[SQis]' in 1/10, `xsize' `ysize' nodraw
		local xdim `.Graph._scheme.graphsize.x'
		local ydim `.Graph._scheme.graphsize.y'
		if `ydim' > `xdim' local ydim = `ydim' * `ydim'/`xdim'

		// procede as shown in Kohler/Brzinsky (2005)
		levelsof `_dta[SQis]', local(K)
		local i 1
		local j 2
		foreach k of local K {
			local suffix: subinstr local k "-" "M", all
			local suffix: subinstr local suffix "." "X", all
			tempvar bsq`suffix' esq`suffix'
			gen `bsq`suffix'' = `begin' if `_dta[SQis]' == `k'
			gen `esq`suffix'' = `end' if `_dta[SQis]' == `k'
			local legorder  `"`legorder' `j' `"`:label (`_dta[SQis]') `k''"'"'

			if `"`color'"' != `""' {
				local colopt1 `"lcolor(`"`:word `i' of `color''"')"'
				local colopt2 `"color(`"`:word `i' of `color''"')"'
			}
			else local colopt2 bstyle(p`i')

			if "`device'" == "rspike" {
				local deviceopt 		///
				  lwidth(`=1/_N*`overplot'*`ydim'') lstyle(p`i'bar) `colopt1'
			}
			else if "`device'" == "rbar" {
				local deviceopt  `colopt2'
			}

			local rbars `rbars' 		///
			  ||  `device' `bsq`suffix'' `esq`suffix'' `ordervar'  ///
			  , horizontal `deviceopt'                             ///
			  ||  rbar `legend1' `legend2' `ordervar'              ///
			  , horizontal `colopt2'
			local j = `j'+2
			local i = `i'+1
		}

		// Graph defaults
		if `"`xscale'"' == `""' {
			sum `begin', meanonly
			local min = r(min)
			sum `end', meanonly
			local max = r(min)
			local xscale "xscale(range(`min' `max'))"
		}

		if `"`yscale'"' == `""' local yscale `"yscale(reverse)"'
		else local yscale `"yscale(`yscale')"'

		if `"`xtitle'"' == "" local xtitle `"xtitle("")"'
		else local xtitle `"xtitle(`xtitle')"'

		if `"`legend'"' == `""' 		///
		  local legend `"legend(order(`legorder') col(1) pos(2))"'
		if `"`by'"' != `""' {
			if `"`byopts'"' == `""' local byopts `", legend(pos(2) yrescale)"'
			local by `"by(`byvars' `byopts')"'
		}


		// The graph
		graph twoway `rbars' , ///
		  `legend' `by' ///
		  `yscale' ylab(, angle(horizontal)) ytitle(`""') ///
		  `xscale' `options' `xtitle' `ysize' `xsize'
	}
end

program CheckConstant, rclass
	syntax varlist(default=none) [, stop]
	sort `_dta[SQiis]'
	foreach var of local varlist {
		capture by `_dta[SQiis]': assert `var' == `var'[_n-1] if _n != 1
		if _rc & "`stop'" == "" {
			di as res "`var'" as text " is not constant over time; not used"
			local varlist: subinstr local varlist "`var'" "", word
		}
		if _rc & "`stop'" != "" {
			di as error "`var' is not constant over time"
			exit 9
		}
		if "`stop'" == "" {
			return local checked "`varlist'"
		}
	}
end

// Selects Ranks according to rank-Options
program KeepRanks
	syntax varname, ranks(string)
	tempvar rank tieshelp tiesrank select
	by `varlist', sort: gen int `rank' = _n==1
	gen int `tieshelp' = _N+1 - _n
	replace `rank' = sum(`rank')
	replace `rank' = `rank'[_N] +1  - `rank'
	sort `tieshelp'
	gen `tiesrank' = `tieshelp' if `rank'!=`rank'[_n-1] & `rank' <= `tieshelp'
	by `rank', sort: replace `rank' = `tiesrank'[1]
	gen int `select' = 0
	foreach r of local ranks {
		replace `select' = 1 if `rank'  == `r'
	}
	keep if `select'
end