*! trimap v1.0 (12 Sep 2024)
*! Asjad Naqvi (asjadnaqvi@gmail.com)


* v1.0 (28 Sep 2024) : Beta release


program trimap, sortpreserve

	version 15

	syntax varlist(min=3 max=3 numeric) [if] [in]  ///
		[ , frame(string) cuts(real 5) showlabel LColor(string) LWidth(string) format(str)  ] ///
		[ msize(string) malpha(real 90) MLColor(string) MLWIDth(string) MColor(string) MSYMbol(string) TICKSize(string) LABColor(string) ]	///
		[ LEGLColor(string) LEGLwidth(string) ] ///
		[ colorR(string) colorL(string) colorB(string)  ] ///
		[ fill points lines labels geo(string) geopost(string) 	 ]	///
		[ MLABel(varlist max=1) MLABSize(string) MLABColor(string) MLABPOSition(string) NORMalize(string) ] ///
		[ zoom xscale(real 50) yscale(real 100) * ]




	if "`frame'" != "" frame change `frame'

	marksample touse, strok


	if "`colorB'" == "" local colorB #DCB600
	if "`colorR'" == "" local colorR #FF6CFF
	if "`colorL'" == "" local colorL #00E0DF


qui {
	// save the tempfile with ids
	preserve
		clear
		tempfile _points
		qui _ternary_triangles, cuts(`cuts') colorB(`colorB') colorR(`colorR') colorL(`colorL')
		keep if _tag==1
		keep _id color
		ren _id tri_id
		save `_points'
	restore



	// main routine here
preserve
	keep if `touse'
	keep _ID `varlist'


	tokenize `varlist'

	lab var `1' `1'
	lab var `2' `2'
	lab var `3' `3'

	ren `1' _R
	ren `2' _L
	ren `3' _B

	// run checks

	egen _check = rowtotal(_R _L _B)

	if "`normalize'" == "1" {
		replace _R = _R / _check
		replace _L = _L / _check
		replace _B = _B / _check
		local normlvl = 1

	}
	else if "`normalize'" == "100"  { // default
		replace _R = (_R / _check) * 100
		replace _L = (_L / _check) * 100
		replace _B = (_B / _check) * 100
		local normlvl = 100

	}
	else {
		summ _check, meanonly

		if round(`r(max)') == 1 {
			noisily display in yellow "Normalization of 1 assumed."
			local normlvl = 1
		}
		else if round(`r(max)') == 100 {
			noisily display in yellow "Normalization of 100 assumed."
			local normlvl = 100
		}
		else {
			noisily display in red "Variables do not add up to 1 or 100. Either normalize the variables or use option {ul:norm(1)} or {ul:norm(100)}."
			exit
		}


		replace _L = _L / `normlvl'
		replace _R = _R / `normlvl'
		replace _B = _B / `normlvl'

	}

	drop _check

	if "`format'" == "" {
		if `normlvl' == 1 	local format  %5.2f
		if `normlvl' == 100 local format  %6.0f
	}



	local mymax = 1
	local mymin = 0

	if "`zoom'" != "" {
		local mymin = 0

		local i = 1
		foreach x in _R _L _B {
			summ `x', meanonly

			if `mymin' < `r(min)' {
				local mymin = (floor(`r(min)' * 100) / 100)
				local myvar `x'
			}

			local ++i
		}

		// normalize
		local others "_R _L _B"
		local remove "`myvar'"

		local mymax = 1

		local others : list others - remove

		foreach x of local others {
			summ `x', meanonly

			if `mymax' > `r(min)' {
				local mymax = (floor(`r(min)' * 100) / 100)
			}
		}

		local mymax = 1 - `mymax'

		foreach x of local others {
			replace `x' = (`x' - (1 - `mymax')) / (`mymax' - `mymin')
		}

		replace `myvar' = (`myvar' - `mymin' ) / (`mymax' - `mymin')

	}


	**** barycentric coordinates

	gen double _yvar = _R * sqrt(3) / 2
	gen double _xvar = 1 - (_R/2 + _L)


	// assign triangles to points

	gen _seq = _n

	gen double _ytr = _yvar * 2 / sqrt(3)
	gen double _xtr = _xvar - (_ytr / 2)

	gen tri_id = .

	gen skip = .

	replace _ytr = _ytr * `cuts'
	replace _xtr = _xtr * `cuts'



	*** up triangles

	local counter = 1

	forval i = 1/`cuts' {

		local y1 = `cuts' - `i'
		local y2 = `cuts' - `i' + 1
		local j = 1

		while `j' <= `i'  {
			local x1 = `j' - 1
			local x2 = `j'

			if `i' == 1 {
				local id = 1
			}
			else {
				local id = 2 * `counter' - `i'
			}


			gen double A1 = abs((_xtr*(`y1' - `y2') + `x2'*(`y2' - _ytr) + `x1'*(_ytr - `y1')) ) if missing(tri_id)
			gen double A2 = abs((`x1'*(_ytr - `y2') + _xtr*(`y2' - `y1') + `x1'*(`y1' - _ytr)) ) if missing(tri_id)
			gen double A3 = abs((`x1'*(`y1' - _ytr) + `x2'*(_ytr - `y1') + _xtr*(`y1' - `y1')) ) if missing(tri_id)

			gen double temp_`id' = round((A1+A2+A3) * 10) / 10
			replace tri_id = `id' if missing(tri_id) & temp_`id'==1

			cap drop A1 A2 A3 temp_`id'


			local ++j
			local ++counter
		}
	}


	*** down triangles

	local counter = 1

	forval i = 1/`=`cuts'-1' {

		local y1 = `cuts' - `i'
		local y2 = `cuts' - `i' - 1

		local j = 1



		while `j' <= `i'  {
			local x1 = `j' - 1
			local x2 = `j'

			if `i' == 1 {
				local id = 3
			}
			else {
				local id = 2 * `counter' + `i'
			}


			gen double A1 = abs((_xtr*(`y1' - `y2') + `x2'*(`y2' - _ytr) + `x2'*(_ytr - `y1')) ) if missing(tri_id)
			gen double A2 = abs((`x1'*(_ytr - `y2') + _xtr*(`y2' - `y1') + `x2'*(`y1' - _ytr)) ) if missing(tri_id)
			gen double A3 = abs((`x1'*(`y1' - _ytr) + `x2'*(_ytr - `y1') + _xtr*(`y1' - `y1')) ) if missing(tri_id)

			gen double temp_`id' = round((A1+A2+A3) * 10) / 10
			replace tri_id = `id' if missing(tri_id) & temp_`id'==1
			cap drop A1 A2 A3 temp_`id'

			local ++j
			local ++counter
		}
	}



		merge m:1 tri_id using `_points'
		sort _seq


		drop if _m==2
		drop _merge
		drop _ytr _xtr


		local k = 1
		levelsof tri_id, local(lvls)

		foreach x of local lvls {


			summ _seq if tri_id==`x', meanonly


					local myclr = color[`r(min)']

			if `k'==1 {
				local clrlist = `myclr' + char(34)
			}
			else {
				local clrlist `clrlist' `myclr'
			}



			local ++k
		}

			local clrlist = char(34) + `"`clrlist'"'




	geoplot ///
		(area `frame' i.tri_id, color(`clrlist') lcolor(`lcolor') lwidth(`lwidth') )  ///
		`geo'	///
		, `geopost' tight legend(off) name(_map, replace) nodraw

	*/

restore



	preserve

		if "`points'" == "" & "`fill'" =="" local points points

		ternary `varlist', cuts(`cuts') mcolor(`mcolor') malpha(`malpha') msize(`msize') `fill' `zoom' `points' `lines' `labels' colorB(`colorB') colorR(`colorR') colorL(`colorL') ///
		fxsize(`xscale') fysize(`yscale') lcolor(`leglcolor') lwidth(`leglwidth') msymbol(`msymbol') ticksize(`ticksize') mlcolor(`mlcolor') mlwidth(`mlwidth') labcolor(`labcolor')  ///
		mlabel(`mlabel') mlabcolor(`mlabcolor') mlabsize(`mlabsize') mlabpos(`mlabposition') normalize(`normalize') format(`format') ///
		name(_legend, replace) nodraw

	restore


	*** put the graphs together

	graph combine _map _legend, imargin(zero) `options'


		*/

*	restore
}

end

**************************
**** return triangles ****
**************************

cap program drop _ternary_triangles
program _ternary_triangles
version 15

	syntax , cuts(numlist max=1) colorB(string) colorR(string) colorL(string)

	set obs `=(`cuts'^2) * 5'

	gen _id = .
	gen double _x  = .
	gen double _y  = .
	gen _control = .

	gen B_seg = .
	gen R_seg = .
	gen L_seg = .
	gen _tag = .


	local counter = 1

	forval i = 1/`cuts' {

		local y1 = `cuts' - `i'
		local y2 = `cuts' - `i' + 1
		local j = 1

		while `j' <= `i'  {
			local x1 = `j' - 1
			local x2 = `j'

			if `i' == 1 {
				local id = 1
			}
			else {
				local id = 2 * `counter' - `i'
			}

			local start = (`id'-1)*5 + 1

			replace _x = `x1' in `start'
			replace _y = `y1' in `start'

			replace _x = `x2' in `=`start'+1'
			replace _y = `y1' in `=`start'+1'

			replace _x = `x1' in `=`start'+2'
			replace _y = `y2' in `=`start'+2'

			replace _x = `x1' in `=`start'+3'
			replace _y = `y1' in `=`start'+3'


			// controls
			replace _id 	 = `id' in `start'/`=`start'+4'
			replace _control = 0 	in `start'/`=`start'+4'

			// segments
			replace B_seg = `x2'  	in `start'
			replace R_seg = `y2'	in `start'

			replace L_seg = `cuts' - ( `x2' + `y2' - 2) in `start'
			replace _tag = 1 in `start'

			local ++j
			local ++counter
		}
	}


	local counter = 1

	forval i = 1/`=`cuts'-1' {

		local y1 = `cuts' - `i'
		local y2 = `cuts' - `i' - 1

		local j = 1

		while `j' <= `i'  {
			local x1 = `j' - 1
			local x2 = `j'

			if `i' == 1 {
				local id = 3
			}
			else {
				local id = 2 * `counter' + `i'
			}

			local start = (`id'-1)*5 + 1

			replace _x = `x1' in `start'
			replace _y = `y1' in `start'

			replace _x = `x2' in `=`start'+1'
			replace _y = `y1' in `=`start'+1'

			replace _x = `x2' in `=`start'+2'
			replace _y = `y2' in `=`start'+2'

			replace _x = `x1' in `=`start'+3'
			replace _y = `y1' in `=`start'+3'

			replace B_seg = `x2'	in `start'
			replace R_seg = `y1'	in `start'
			replace L_seg = `cuts' - ( `x2' + `y1' - 1) in `start'

			replace _tag     = 1    in `start'
			replace _id      = `id' in `start'/`=`start'+4'
			replace _control = 1    in `start'/`=`start'+4'

			local ++j
			local ++counter
		}
	}

	drop if _id==.


	**** normalize

	replace _x = _x / `cuts'
	replace _y = _y / `cuts'


	**** transform

	gen double x2 = _x + (_y/2)
	gen double y2 = _y * sqrt(3)/2

	drop _x _y


	**** generate centroids

	bysort _id: gen seq = _n

	bysort _id: egen double x2c = mean(x2) if inrange(seq, 1, 3)
	bysort _id: egen double y2c = mean(y2) if inrange(seq, 1, 3)


	*** generate ratios of color strength

	cap drop *_share

	gen double B_share = .
	gen double L_share = .
	gen double R_share = .


	foreach x in B L R {
		replace `x'_share = (`x'_seg - 1) / (`cuts' - 1) if _control==0
		replace `x'_share = ((2 *(`x'_seg - 1) + `x'_seg) / 3) / (`cuts' - 1)   if _control==1
	}


	***** let's start adding colors

	cap drop color
	gen str15 color = ""


	levelsof _id, local(lvls)

	foreach x of local lvls {
		summ B_share if _id==`x' & _tag==1, meanonly
		colorpalette `colorB', intensify(`r(mean)') nograph
		tokenize `r(p1)'
		args b1 b2 b3

		summ L_share if _id==`x' & _tag==1, meanonly
		colorpalette `colorL', intensify(`r(mean)') nograph
		tokenize `r(p1)'
		args l1 l2 l3

		summ R_share if _id==`x' & _tag==1, meanonly
		colorpalette `colorR', intensify(`r(mean)') nograph
		tokenize `r(p1)'
		args r1 r2 r3

		local c1 = floor((`b1' * `r1' * `l1') / (255 * 255))
		local c2 = floor((`b2' * `r2' * `l2') / (255 * 255))
		local c3 = floor((`b3' * `r3' * `l3') / (255 * 255))

		replace color = char(34) + "`c1' `c2' `c3'" + char(34) if _id==`x' & _tag==1

	}


end






**** END ****