* tmap_choropleth NJC 1.1.0 14 July 2004
*! -tmap_choropleth-: Choropleth maps
*! Version 1.2 - 23 July 2004
*! Version 1.0 - 24 January 2004
*! Author: Maurizio Pisati
*! Department of Sociology and Social Research
*! University of Milano Bicocca (Italy)
*! maurizio.pisati@unimib.it




*  ----------------------------------------------------------------------------
*  1. Define program
*  ----------------------------------------------------------------------------

program tmap_choropleth
version 8.2




*  ----------------------------------------------------------------------------
*  2. Define syntax
*  ----------------------------------------------------------------------------

syntax varname(numeric) [if] [in],                 ///
       Id(varname numeric)                         ///
       Map(string)                                 ///
      [CLMethod(string)]                           ///
      [CLNumber(integer 4)]                        ///
      [CLBreaks(numlist min=3 max=10 ascending)]   ///
      [EIRange(numlist min=2 max=2 ascending)]     ///
      [Palette(string)]                            ///
      [Colors(string)]                             ///
      [OColor(string)]                             ///
      [OSize(string)]                              ///
      [LEGPos(integer 7)]                          ///
      [LEGTitle(string)]                           ///
      [LEGFormat(string)]                          ///
      [LEGBox]                                     ///
      [noLEGend]

/*
CLMethod(quantile | eqint | stdev | custom | unique)
CLNumber(min=2 max=9)
Palette(Blues | BrBG | Greens | Greys | Paired | PuRd |
        Purples | RdBu | RdGy | Reds | Set1 | Set3 |
        YlOrBr | Custom)
        Defaults: Greys  if CLMethod(quantile | eqint | custom)
                  RdBu   if CLMethod(stdev)
                  Paired if CLMethod(unique)
*/




*  ----------------------------------------------------------------------------
*  3. Check syntax
*  ----------------------------------------------------------------------------

/* Marksample */
marksample touse
qui count if `touse'
if r(N) == 0 error 2000

/* Check option map() */
capture confirm file `"`map'"'
if _rc {
	di as err ///
	"{p}file `map' specified in option {bf:{ul:m}ap()} does not exist{p_end}"
	exit 498
}

/* Check option clmethod() */
if "`clmethod'" != "" {
	local LIST "quantile eqint stdev custom unique"
	local EXIST : list posof `"`clmethod'"' in LIST
	if !`EXIST' {
		di as err ///
		"{p}option {bf:{ul:clm}ethod()} accepts only one of the following keywords: {bf:`LIST'}{p_end}"
		exit 198
	}
}

/* Check option clmethod() */
if "`clmethod'" == "unique" {
	qui tab `varlist' if `touse'
	if r(r) < 2 {
		di as err "variable `varlist' has too few values"
		exit 498
	}
	else if r(r) > 9 {
		di as err "variable `varlist' has too many values"
		exit 498
	}
}

/* Check option clnumber() */
if !inrange(`clnumber', 2, 9) {
	di as err ///
	"{p}option {bf:{ul:cln}umber()} accepts only values between 2 and 9{p_end}"
	exit 198
}

/* Check option clbreaks() */
if "`clmethod'" == "custom" & "`clbreaks'" == "" {
	di as err "{p}if you specify option {bf:{ul:clm}ethod(custom)} you must " _c
	di as err "specify also option {bf:{ul:clb}reaks()}{p_end}"
	exit 198
}

/* Check option palette() */
if "`palette'"!="" {
	local LIST "Blues BrBG Greens Greys Paired PuRd Purples"
	local LIST "`LIST' RdBu RdGy Reds Set1 Set3 YlOrBr Custom"
	local EXIST : list posof `"`palette'"' in LIST
	if !`EXIST' {
		di as err "{p}option {bf:{ul:p}alette()} accepts only one of the " _c
		di as err "following keywords: {bf:`LIST'}{p_end}"
		exit 198
	}
}

/* Check option colors() */
if "`palette'" == "Custom" & "`colors'" == "" {
	di as err "{p}if you specify option {bf:{ul:p}alette(Custom)} you must " _c
	di as err "specify also option {bf:{ul:c}olors()}{p_end}"
	exit 198
}

/* Check option colors() */
if "`palette'" == "Custom" & inlist("`clmethod'","quantile","eqint","stdev") {
	local NCOLORS : word count `colors'
	if `clnumber' != `NCOLORS' {
		di as err "you must specify `clnumber' different colors in option {bf:{ul:c}olors()}"
		exit 198
	}
}

/* Check option colors() */
if "`palette'" == "Custom" & "`clmethod'" == "custom" {
	local NCLASSES : word count `clbreaks'
	local NCLASSES = `NCLASSES' - 1
	local NCOLORS : word count `colors'
	if `NCLASSES' != `NCOLORS' {
		di as err "you must specify `NCLASSES' different colors in option {bf:{ul:c}olors()}"
		exit 198
	}
}

/* Check option colors() */
if "`palette'" == "Custom" & "`clmethod'" == "unique" {
	qui tab `varlist' if `touse'
	local NCLASSES = r(r)
	local NCOLORS : word count `colors'
	if `NCLASSES' != `NCOLORS' {
		di as err "{p}you must specify `NCLASSES' different colors in option {bf:{ul:c}olors()}{p_end}"
		exit 198
	}
}

/* Check option legpos() */
if `legpos' < 1 | `legpos' > 12 {
	di as err "{p}option {bf:{ul:legp}os()} accepts only values between 1 and 12{p_end}"
	exit 198
}

/* Check option legformat() */
if "`legformat'"!="" {
	capture qui format `varlist' `legformat'
	if _rc {
		di as err "{p}`legformat' in option {bf:{ul:legf}ormat()} is not a valid format{p_end}"
		exit 198
	}
}




*  ----------------------------------------------------------------------------
*  4. Define basic objects
*  ----------------------------------------------------------------------------

/* Preserve data */
preserve

/* Set default classification method */
if "`clmethod'" == "" local clmethod "quantile"

/* Set default color palettes */
if "`palette'" == "" {
	if "`clmethod'" == "quantile"    local palette "Greys"
	else if "`clmethod'" == "eqint"  local palette "Greys"
	else if "`clmethod'" == "custom" local palette "Greys"
	else if "`clmethod'" == "stdev"  local palette "RdBu"
	else if "`clmethod'" == "unique" local palette "Paired"
}

/* Set default outline color */
if "`ocolor'" == "" local ocolor "black"

/* Set default outline thickness */
if "`osize'" == "" local osize "thin"

/* Set default legend format */
if "`legformat'" == "" local legformat "%8.2f"

/* Keep only relevant cases */
qui keep if `touse'

/* Keep only relevant variables */
keep `id' `varlist'
cap rename `id' _id
cap rename `varlist' _attribute

/* Define attribute range */
if "`clmethod'" != "eqint" {
	su _attribute, meanonly
	local VMIN = r(min)
	local VMAX = r(max)
}
else if "`clmethod'" == "eqint" {
	if "`eirange'" == "" {
		su _attribute, meanonly
		local VMIN = r(min)
		local VMAX = r(max)
	}
	else {
		local VMIN : word 1 of `eirange'
		local VMAX : word 2 of `eirange'
	}
}




*  ----------------------------------------------------------------------------
*  5. Create class variable
*  ----------------------------------------------------------------------------

/* Set number of classes */
local NC = `clnumber'
if "`clmethod'" == "custom" {
	local NC : word count `clbreaks'
	local NC = `NC' - 1
}
else if "`clmethod'" == "unique" {
	qui tab _attribute
	local NC = r(r)
}

/* Quantile method */
if "`clmethod'" == "quantile" {
	qui pctile _cutpoints = _attribute, nq(`NC')
	qui xtile _class = _attribute, cutpoints(_cutpoints)
	local CBREAKS "`VMIN'"
	forval i = 1/`=`NC'-1' {
		local CB = _cutpoints[`i']
		local CBREAKS "`CBREAKS' `CB'"
	}
	local CBREAKS "`CBREAKS' `VMAX'"
}

/* Equal interval method */
if "`clmethod'" == "eqint" {
	local INTERVAL = (`VMAX' - `VMIN') / `NC'
	local CBREAKS "`VMIN'"
	forval i = 1 / `=`NC'-1' {
		local CB = `: word `i' of `CBREAKS'' + `INTERVAL'
		local CBREAKS "`CBREAKS' `CB'"
	}
	local CBREAKS "`CBREAKS' `VMAX'"
	qui gen _class = .
	local LOWER : word 1 of `CBREAKS'
	local UPPER : word 2 of `CBREAKS'
	qui replace _class = 1 if inrange(float(_attribute), float(`LOWER'), float(`UPPER'))
	forval ii = 3 /`= `NC' + 1' {
		local i = `ii' - 1
		local LOWER : word `i' of `CBREAKS'
		local UPPER : word `ii' of `CBREAKS'
		qui replace _class = `i' if float(_attribute)>float(`LOWER') & float(_attribute)<=float(`UPPER')
	}
}

/* Standard deviation method */
if "`clmethod'"=="stdev" {
	qui su _attribute
	local VMEAN = r(mean)
	local VSD   = r(sd)
	if `NC' == 2 local CBLIST "`VMIN' `VMEAN' `VMAX'"
	if `NC' > 2 {
		local LIM  "0.6 1.0 1.2 1.6 2.0 1.8 2.1"
		local WID "1.2 1.0 0.8 0.8 0.8 0.6 0.6"
		local K=`NC'-2
		local L : word `K' of `LIM'
		local W : word `K' of `WID'
		numlist "-`L'(`W')`L'"
		local NLIST "`r(numlist)'"
		local CBLIST "`VMIN'"
		forval i = 1/`=`NC'-1' {
			local CB = `: word `i' of `NLIST''*`VSD'+`VMEAN'
			local CBLIST "`CBLIST' `CB'"
		}
		local CBLIST "`CBLIST' `VMAX'"
	}
	qui gen _class = .
	local LOWER : word 1 of `CBLIST'
	local UPPER : word 2 of `CBLIST'
	qui replace _class = 1 if inrange(float(_attribute), float(`LOWER'), float(`UPPER'))
	forval ii = 3 / `= `NC' + 1' {
		local i = `ii' - 1
		local LOWER : word `i' of `CBLIST'
		local UPPER : word `ii' of `CBLIST'
		qui replace _class = `i' if float(_attribute)>float(`LOWER') & float(_attribute)<=float(`UPPER')
	}
	local CBREAKS ""
	forval i = 1/`=`NC'+1' {
		local CB : word `i' of `CBLIST'
		if `CB' < `VMIN' local CB = `VMIN'
		if `CB' > `VMAX' local CB = `VMAX'
		local CBREAKS "`CBREAKS'`CB' "
	}
}

/* Custom class breaks */
if "`clmethod'" == "custom" {
	local CBREAKS "`clbreaks'"
	qui gen _class = .
	local LOWER : word 1 of `CBREAKS'
	local UPPER : word 2 of `CBREAKS'
	qui replace _class = 1 if inrange(float(_attribute), float(`LOWER'), float(`UPPER'))
	forval ii = 3 / `= `NC' + 1' {
		local i = `ii' - 1
		local LOWER : word `i' of `CBREAKS'
		local UPPER : word `ii' of `CBREAKS'
		qui replace _class = `i' if float(_attribute)>float(`LOWER') & float(_attribute)<=float(`UPPER')
	}
}

/* Unique values */
if "`clmethod'" == "unique" {
	qui egen _class = group(_attribute), label lname(_class)
}

/* Drop cases with missing class */
qui drop if missing(_class)




*  ----------------------------------------------------------------------------
*  6. Create list of units to be mapped
*  ----------------------------------------------------------------------------

tempname UNITS
qui tab _id, matrow(`UNITS')
local N = r(r)

local INT = int(`N' / 50)
local MOD = mod(`N', 50)
local C = `INT' + 1
forval i = `INT'(-1)1 {
	local FROM = `N' - `MOD' - (`i' * 50) + 1
	local TO = (`N' - `MOD') / `i'
	local c = `INT' + 1 - `i'
	local INLIST`c'
	forval j = `FROM' / `= `TO' -1' {
		local X = `UNITS'[`j',1]
		local INLIST`c' "`INLIST`c''`X',"
	}
	local X = `UNITS'[`TO',1]
	local INLIST`c' "`INLIST`c''`X'"
}

local FROM = (`INT' * 50) + 1
local TO = `N' - 1
local INLIST`C'
forval j = `FROM' / `TO' {
	local X = `UNITS'[`j',1]
	local INLIST`C' "`INLIST`C''`X',"
}

local X = `UNITS'[`N',1]
local INLIST`C' "`INLIST`C''`X'"




*  ----------------------------------------------------------------------------
*  7. Set colors
*  ----------------------------------------------------------------------------

if "`palette'" != "Custom" {
	tempname RGB
	matrix `RGB' = J(`NC',3,0)
	get_palette "`palette'" "`RGB'" `NC'
	forval i=1 / `NC' {
		local R = `RGB'[`i',1]
		local G = `RGB'[`i',2]
		local B = `RGB'[`i',3]
		local COLORS `"`COLORS' "`R' `G' `B'""'
	}
}
else if "`palette'" == "Custom" local COLORS `"`colors'"'




*  ----------------------------------------------------------------------------
*  8. Make up legend
*  ----------------------------------------------------------------------------

qui sort _class _attribute
qui gen temp1 = _n
by _class: gen temp2 = _n == 1
local KEYS
local YES
forval i = 1 / `N' {
	local X = temp1[`i'] * temp2[`i']
	if `X' > 0 local KEYS "`KEYS'`X' "
	local X = _class[`i'] * temp2[`i']
	if `X' > 0 local YES "`YES'`X' "
}

forval ii = 2 / `= `NC' + 1' {
	local i = `ii' - 1
	if "`clmethod'" != "unique" {
		local LOWER : word `i' of `CBREAKS'
		local UPPER : word `ii' of `CBREAKS'
		local LOWER = string(`LOWER', "`legformat'")
		local UPPER = string(`UPPER', "`legformat'")
		if `i' == 1 {
			local RANGE `"`RANGE'`"[`LOWER',`UPPER']"' "'
		}
		else if `i' > 1 {
			local RANGE `"`RANGE'`"(`LOWER',`UPPER']"' "'
		}
	}
	else if "`clmethod'"=="unique" {
		local LBL : label (_class) `i'
		local RANGE `"`RANGE'"`LBL'" "'
	}
}

local w = 1
forval i = 1 / `NC' {
	local Y : list i in YES
	if `Y' == 1 {
		local K : word `w' of `KEYS'
		local R : word `i' of `RANGE'
		local LABEL `"`LABEL'lab(`K' `"`R'"') "'
		local w = `w' + 1
	}
}

local BOX = cond("`legbox'" == "", "none", "0 0 0")

if "`legtitle'" != "" local TITLE `"subtitle("`legtitle'", size(*0.5))"'

if "`legend'" == "" {
	local LEGEND `"legend( cols(1) order(`KEYS') `LABEL' size(*0.5)"'
	local LEGEND `"`LEGEND' region(lc("`BOX'") fc(none)) `TITLE'"'
	local LEGEND `"`LEGEND' symy(1.7) symx(3) keygap(1) rowgap(0.5) forces"'
	local LEGEND `"`LEGEND' ring(0) position(`legpos') )"'
}
else local LEGEND "legend(off)"

tempname ID
qui mkmat _id _class, matrix(`ID')




*  ----------------------------------------------------------------------------
*  9. Draw map
*  ----------------------------------------------------------------------------

/* Calculate plot region */
qui use `"`map'"', clear
forval c = 1 / `= `C' - 1' {
	local INLIST "`INLIST'inlist(_ID,`INLIST`c'') | "
}
qui keep if `INLIST' inlist(_ID,`INLIST`C'')

su _Y, meanonly
local ymin = r(min)
local ymax = r(max)
su _X, meanonly
local xmin = r(min)
local xmax = r(max)
local JY = (`ymax' - `ymin') * 0.03
local JX = (`xmax' - `xmin') * 0.03
local ymin = `ymin' - `JY'
local ymax = `ymax' + `JY'
local xmin = `xmin' - `JX'
local xmax = `xmax' + `JX'
local RATIO = (`ymax' - `ymin') / (`xmax' - `xmin')
local YS = 4
local XS = 4 / `RATIO'

/* Draw map */
local OS "`osize'"
forval i = 1 / `N' {
	local U = `ID'[`i',1]
	local C = `ID'[`i',2]
	local FC : word `C' of `COLORS'
	local OC "`ocolor'"
   if "`OC'" == "none" local OC "`FC'*0.8"
   local GRAPHS "`GRAPHS'(area _Y _X if _ID==`U', nodropbase blc("`OC'") blw(`OS') bfc("`FC'")) "
}

cap graph twoway `GRAPHS', ysize(`YS') xsize(`XS')                    ///
          yscale(r(`ymin' `ymax') off) xscale(r(`xmin' `xmax') off)   ///
	       ylabel(`ymin' `ymax') xlabel(`xmin' `xmax')                 ///
        	 ytitle("") xtitle("")                                       ///
	       plotregion(style(none)) scheme(s1mono) `LEGEND' `options'

if _rc {
	di as err "{p}invalid syntax{p_end}"
	exit _rc
}




*  ----------------------------------------------------------------------------
*  10. End program
*  ----------------------------------------------------------------------------

restore
end




*  ----------------------------------------------------------------------------
*  A1. Subprogram -get_palette-
*  ----------------------------------------------------------------------------

program get_palette
version 8.2
args PALETTE RGB NC


preserve
qui {
	cap findfile "colorschemes.dta"
	use `"`r(fn)'"', clear
	keep if scheme == "`PALETTE'" & classnum == `NC'
	mkmat rgb*, matrix(`RGB')
}
restore


end