*!plssemplot version 0.3.0
*!Written 27Apr2018
*!Written by Sergio Venturini and Mehmet Mehmetoglu
*!The following code is distributed under GNU General Public License version 3 (GPL-3)

program plssemplot
	version 15.1

	if (("`e(cmd)'" != "plssem") & ("`e(cmd)'" != "plssemc")) {
		error 301
	}

	tempname results eresults
	_return hold `results'
	_return restore `results' , hold
	_estimates hold `eresults', copy restore
	capture nobreak noisily {
		_plssemplot `0'
	}
	local rc = _rc
	_return restore `results'
	_estimates unhold `eresults'
	if (`rc') {
		exit `rc'
	}
end

program _plssemplot
	syntax [, INnermodel OUTermodel STats(varlist min=1 max=1) SCores ///
		Crossloadings Loadings OUTERWeights ]
	local 0 `", `options'"'

	tempvar __touse__
	quietly generate `__touse__' = e(sample)

	// check that estimation sample has not changed
	// checkestimationsample

	local props = e(properties)
	local struct "structural"
	local isstruct : list struct in props
	local rawsum "rawsum"
	local israwsum : list rawsum in props
	local knnimp "knn"
	local isknnimp : list knnimp in props
	local meanimp "mean"
	local ismeanimp : list meanimp in props
	if (`isknnimp') {
		local missing "knn"
	}
	else if (`ismeanimp') {
		local missing "mean"
	}
	else {
		local missing ""
	}

	if ("`innermodel'" != "") {
		if (!`isstruct') {
			display as error "the fitted plssem model includes only the measurement part"
			exit
		}

		tempname adjmat
		mata: `adjmat' = st_matrix("e(adj_struct)")

		preserve

		capture quietly nwset, clear
		if ((_rc != 0) & (_rc != 6001)) {
			display as error "to use the 'innermodel' option you need to install the nwcommands suite"
			display as error "type " _continue
			display as smcl "{stata net install nwcommands-ado.pkg}"
			exit
		}

		quietly nwset, mat(`adjmat') directed labs(`e(lvs)')
		quietly nwplot, lab layout(circle) title("Structural model" " ") ///
			labelopt(mlabposition(0)) nodefactor(4.5) arcstyle(automatic) ///
			scatteropt(msymbol(O)) arrowgap(1) scheme(sj)

		restore
		mata: mata drop `adjmat'
	}

	if ("`outermodel'" != "") {
		display as error "this feature will be available soon! :)"
		/*
		tempname adjmat adjmat_full
		local nlvs : word count `e(lvs)'
		local nmvs : word count `e(mvs)'
		local all_nm "`: colnames e(adj_meas)' `: rownames e(adj_meas)'"
		matrix `adjmat' = J(`nlvs' + `nmvs', `nlvs' + `nmvs', 0)
		matrix `adjmat'[`nlvs' + 1, 1] = e(adj_meas)
		matrix `adjmat'[1, 1] = e(adj_struct)
		matrix rownames `adjmat' = `all_nm'
		matrix colnames `adjmat' = `all_nm'
		mata: `adjmat_full' = st_matrix("`adjmat'")

		preserve

		capture quietly nwset, clear
		if ((_rc != 0) & (_rc != 6001)) {
			display as error "to use the 'outermodel' option you need to install the nwcommands suite"
			display as error "type: net install nwcommands-ado.pkg"
			exit
		}

		quietly nwset, mat(`adjmat_full') directed labs(`all_nm')
		quietly nwplot, lab layout(mds) title("Measurement model" " ") ///
			labelopt(mlabposition(0)) nodefactor(2.5) arcstyle(automatic) ///
			scatteropt(msymbol(O)) arrowgap(1) scheme(sj)

		restore
		mata: mata drop `adjmat_full'
		*/
	}

	if ("`stats'" != "") {
		tempname loads lv
		matrix `loads' = e(loadings)
		local num_ind = rowsof(`loads')
		local num_lv = colsof(`loads')
		local loads_cn : colnames `loads'
		local loads_rn : rownames `loads'
		local pos_lv : list posof "`stats'" in loads_cn
		matrix `lv' = `loads'[1..`num_ind', `pos_lv']
		forvalues i = 1/`num_ind' {
			if (!missing(`lv'[`i', 1])) {
				local ind_lv : word `i' of `loads_rn'
				local block_lv "`block_lv' `ind_lv'"
			}
		}

		if ("`missing'" != "") {
			/* Save original data set */
			local allindicators = e(mvs)
			tempname original_data
			mata: `original_data' = st_data(., "`: list uniq allindicators'", "`__touse__'")

			/* Recovery of missing values */
			mata: st_store(., tokens("`: list uniq allindicators'"), "`__touse__'", ///
				st_matrix("e(imputed_data)"))
		}

		quietly summarize `block_lv' if `__touse__'
		local jsize = (r(max) - r(min))*.15
		if ("`missing'" == "") {
			local stats_title "`stats'"
		}
		else {
			local stats_title "`stats' (imputed)"
		}
		graph matrix `block_lv' if `__touse__', half title(`stats_title') ///
			maxes(ylab(#6, grid) xlab(#6, grid)) msymbol(oh) jitter(`jsize') ///
			jitterseed(1406) scheme(sj)

		/*
		local num_block : word count `block_lv'
		forvalues i = 1/`num_block' {
			forvalues j = 1/`num_block' {
				if (`i' == `j') {
					tempname hist`j'
					local hist_lv : word `j' of `block_lv'
					quietly tabulate `hist_lv' if `__touse__'
					if (r(r) > 10) {
						local type ""
						local freq ""
					}
					else {
						local type "discrete"
						local freq "frequency"
					}
					if (`i' == 1) {
						local xlabel "xlabel(none)"
						local ylabel ""
						local xscale ""
						local yscale ""
					}
					else if (`i' == `num_block') {
						local xlabel ""
						local ylabel ""
						local xscale ""
						local yscale "yscale(alt)"
					}
					else {
						local xlabel "xlabel(none)"
						local ylabel "ylabel(none)"
						local xscale ""
						local yscale ""
					}
					quietly histogram `hist_lv' if `__touse__', `type' name(`hist`j'') ///
						ytitle("") xtitle("") `xlabel' `ylabel' `xscale' `yscale' `freq' ///
						plotregion(lcolor(black) lwidth(thin)) ///
						title(`hist_lv', ring(0)) nodraw scheme(sj)
					local toplot "`toplot' `hist`j''"
				}
				else if (`i' < `j') {
					tempname scatter`i'`j'
					tempvar empty
					quietly generate `empty' = . if `__touse__'
					local sc_x : word `j' of `block_lv'
					local sc_y : word `i' of `block_lv'
					quietly correlate `sc_y' `sc_x' if `__touse__'
					local corr_todisp = string(r(rho), "%9.4g")
					local corr_todisp "r = `corr_todisp'"
					local used = r(N)
					quietly count if `__touse__'
					local size = r(N)
					local N_todisp = string(`used'/`size'*100, "%9.2g")
					local N_todisp "N = `used' (`N_todisp'%)"
					twoway scatter `empty' `empty' if `__touse__', legend(off) ///
						ytitle("") xtitle("") xlabel(none) ylabel(none) ///
						xscale(range(0 2)) yscale(range(0 3)) ///
						text(1.75 1 "`corr_todisp'", size(medium)) ///
						text(1.25 1 "`N_todisp'", size(medium)) ///
						plotregion(lcolor(black) lwidth(thin)) ///
						name(`scatter`i'`j'') nodraw scheme(sj)
					local toplot "`toplot' `scatter`i'`j''"
				}
				else {
					tempname scatter`i'`j'
					local sc_x : word `j' of `block_lv'
					local sc_y : word `i' of `block_lv'
					if ((`i' > 1) & (`i' < `num_block')) {
						local xlabel "xlabel(none)"
						local ylabel ""
						local xscale ""
						local yscale ""
					}
					else if ((`j' == 1) & (`i' == `num_block')) {
						local xlabel ""
						local ylabel ""
						local xscale ""
						local yscale ""
					}
					else if ((`i' > 1) & (`i' == `num_block')) {
						local xlabel ""
						local ylabel "ylabel(none)"
						local xscale ""
						local yscale ""
					}
					else {
						local xlabel "xlabel(none)"
						local ylabel "ylabel(none)"
						local xscale ""
						local yscale ""
					}
					twoway (scatter `sc_y' `sc_x' if `__touse__', jitter(`jsize') ///
						ylab(#6, grid) xlab(#6, grid) msymbol(oh) jitterseed(1406)) ///
						(lfit `sc_y' `sc_x', lpattern(solid)), legend(off) ///
						ytitle("") xtitle("") `xlabel' `ylabel' `xscale' `yscale' ///
						plotregion(lcolor(black) lwidth(thin)) ///
						name(`scatter`i'`j'') nodraw scheme(sj)
					local toplot "`toplot' `scatter`i'`j''"
				}
			}
		}
		graph combine `toplot', imargin(vsmall) ///
			graphregion(margin(l=22 r=22)) title(`stats') scheme(sj)
		*/

		/* Clean up */
		if ("`missing'" != "") {
			mata: st_store(., tokens("`: list uniq allindicators'"), "`__touse__'", ///
				`original_data')
		}
	}

	if ("`scores'" != "") {
		if (!`isstruct') {
			display as error "the fitted plssem model includes only the measurement part"
			exit
		}

		graph matrix `e(lvs)' if `__touse__', half title("PLS-SEM scores") ///
			maxes(ylab(#6, grid) xlab(#6, grid)) msymbol(Oh) scheme(sj)
	}

	if ("`crossloadings'" != "") {
		tempname loads
		matrix `loads' = e(loadings)
		local num_ind = rowsof(`loads')
		local num_lv = colsof(`loads')
		tempname xload C
		matrix `xload' = J(`num_ind', `num_lv', .)
		matrix rownames `xload' = `e(mvs)'
		matrix colnames `xload' = `e(lvs)'
		local j = 1
		local last = `num_ind' + 1
		foreach var in `e(lvs)' {
			quietly correlate `var' `e(mvs)' if `__touse__'
			matrix `C' = r(C)
			matrix `xload'[1, `j'] = `C'[2..`last', 1]
			local ++j
		}
		mktable, matrix(`xload') digits(4) firstcolname("") ///
			title("Cross loadings") firstcolwidth(12) colwidth(9) ///
			hlines(`num_ind') novlines corr

		preserve
		quietly drop _all
		quietly svmat double `xload', names("lv")
		quietly generate indicator = .
		quietly generate block = ""
		quietly generate order = .
		local i 1
		local j 1
		foreach ind in `e(mvs)' {
			quietly replace indicator = `i' in `i'
			quietly replace order = `i' if indicator == `i'
			foreach var in `e(lvs)' {
				if (!missing(`loads'[`i', `j'])) {
					local lv_nm : word `j' of `e(lvs)'
					quietly replace block = "`var'" in `i'
				}
				local ++j
			}
			local lblbar "`lblbar' `i' `ind'"
			local j 1
			local ++i
		}
		quietly generate id = _n
		quietly reshape long lv, i(id) j(latent) string
		local i 1
		foreach var in `e(lvs)' {
			quietly replace latent = "`var'" if latent == "`i'"
			quietly clonevar lv`i' = lv
			quietly replace lv`i' = . if block != "`var'"
			quietly label variable lv`i' "`var'"
			local ++i
		}
		tempname minlv maxlv
		mata: st_numscalar("`minlv'", min(st_data(., "lv?")))
		mata: st_numscalar("`maxlv'", max(st_data(., "lv?")))
		if (`minlv' < -.5) {
			local yline_neg = "dash"
		}
		else {
			local yline_neg = "blank"
		}
		graph bar (asis) lv?, over(indicator, label(angle(90) labsize(small)) ///
			relabel(`lblbar') gap(0)) bargap(-90) by(block latent, imargin(tiny) ///
			note(""/*"Graphs by block and latent variable"*/) ///
			title("Cross loadings") legend(position(12)) iscale(*.885)) ///
			ylabel(``minlv'(.25)`maxlv'', labsize(small) nogrid) ///
			yline(0.5, lpattern(dash) lwidth(thin)) ///
			yline(-0.5, lpattern(`yline_neg') lwidth(thin)) ///
			yline(0, lpattern(solid) lwidth(thin)) legend(rows(1) ///
			region(style(none)) size(3) keygap(1) symxsize(5)) scheme(sj)
		restore
	}

	if ("`loadings'" != "") {
		tempname loads
		matrix `loads' = e(loadings)
		local num_ind = rowsof(`loads')
		local num_lv = colsof(`loads')
		mktable, matrix(`loads') digits(4) firstcolname("") ///
			title("Loadings") firstcolwidth(14) colwidth(14) ///
			hlines(`num_ind') novlines

		tempvar lv indicator
		quietly svmat double `loads', names(`lv')
		quietly generate `indicator' = .
		local i 1
		foreach ind in `e(mvs)' {
			quietly replace `indicator' = `i' in `i'
			local lblbar "`lblbar' `i' `ind'"
			local ++i
		}
		local i 1
		foreach var in `e(lvs)' {
			quietly label variable `lv'`i' "`var'"
			local ++i
		}
		tempname minlv maxlv
		mata: st_numscalar("`minlv'", min(st_data(., "`lv'?")))
		mata: st_numscalar("`maxlv'", max(st_data(., "`lv'?")))
		if (`minlv' < -.7) {
			local yline_neg = "dash"
		}
		else {
			local yline_neg = "blank"
		}
		graph bar (asis) `lv'?, over(`indicator', label(angle(90) labsize(small)) ///
			relabel(`lblbar') gap(*.3)) title("Loadings") nofill ///
			ylabel(``minlv'(.1)`maxlv'', labsize(small) nogrid) ///
			yline(0.7, lpattern(dash) lwidth(thin)) ///
			yline(-0.7, lpattern(`yline_neg') lwidth(thin)) ///
			yline(0, lpattern(solid) lwidth(thin)) legend(position(12) rows(1) ///
			region(style(none)) size(3) keygap(1) symxsize(5)) scheme(sj)
	}

	if ("`outerweights'" != "") {
		if (!`isstruct') {
			display as error "the fitted plssem model includes only the measurement part"
			exit
		}
		if (`israwsum') {
			display as error "the plssem model has been fitted using the rawsum option"
			exit
		}

		tempname ow
		matrix `ow' = e(ow_history)
		local niter = e(iterations)

		preserve

		tempvar iter lv
		quietly drop _all
		quietly svmat double `ow', names(eqcol)
		quietly generate `iter' = _n - 1
		capture quietly reshape long `e(mvs)', i(`iter') j(`lv') string
		if (_rc != 0) {
			display as error "some of the indicators have been used more than once; " _continue
			display as error "the outer weights diagram can't be displayed"
			restore
			exit
		}
		// here we use colors (s2gcolor) to emphasize differences
		twoway (line `e(mvs)' `iter', lpattern(solid)), ///
			by(`lv', legend(position(3)) title("Outer weights convergence") ///
			note("")) xtitle("Iteration") ytitle("Outer weights") xlabel(#`niter') ///
			legend(cols(1) size(vsmall)) scheme(s2gcolor)

		restore
	}
end