*! 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 ****