*! version 1.2.2 01aug2024 Ben Jann program _geoplot_symbol version 16.1 _parse comma lhs 0 : 0 syntax [, _immediate _frameonly(passthru) * ] if "`_immediate'"!="" { __geoplot_symboli `lhs', `_frameonly' `options' } else { __geoplot_symbol `lhs', `_frameonly' `options' } if `"`_frameonly'"'!="" exit c_local plot `plot' c_local p `p' end program __geoplot_symboli gettoken layer 0 : 0 gettoken p 0 : 0, parse(", ") _parse comma values 0 : 0 syntax [, _frameonly(str) SIze(str) * ] local PLOT = `"`_frameonly'"'=="" if `PLOT' tempname frame else gettoken frame _frameonly : _frameonly tempname SIZE RELSIZE if `PLOT' local mlab POS str1 LAB frame create `frame' byte _ID _CX _CY `SIZE' `RELSIZE' `mlab' frame `frame' { _symboli `"`size'"' `SIZE' `RELSIZE' `PLOT' `values' char _dta[GEOPLOT_sourcename] symbol } if `haslab' { local mlabel mlabel(LAB) if `haspos' local mlabvpos mlabvpos(POS) } if `"`shparg'"'!="" { local options shparg(`shparg') `options' } ___geoplot_symbol `layer' `p' `frame', size(`SIZE' `RELSIZE')/* */ _frameonly(`_frameonly') `mlabel' `mlabvpos' `options' if !`PLOT' exit c_local plot `plot' c_local p `p' end program _symboli gettoken Size 0 : 0 gettoken SIZE 0 : 0 gettoken RELSIZE 0 : 0 gettoken PLOT 0 : 0 // process size() if `"`Size'"'!="" { if substr(`"`Size'"',1,1)=="*" { local Size = substr(`"`Size'"',2,.) local Relsize 1 } else local Relsize 0 capt confirm number `Size' if _rc==1 exit 1 else if _rc { di as error "size(): invalid syntax" exit 198 } } else { local Size 1 local Relsize 1 } // get optional shape argument gettoken shparg : 0, match(paren) if "`paren'"=="(" { gettoken shparg 0 : 0, match(paren) c_local shparg `"`shparg'"' } // collect values and labels local xy X Y local n 0 local HASLAB 0 local HASPOS 0 if `: list sizeof 0'==0 local 0 while (`"`0'"'!="") { // #_x #_y ... foreach v of local xy { gettoken `v' 0 : 0, qed(q) match(p) if `q' | "`p'"!="" _symboli_err `PLOT' capt numlist `"``v''"', min(0) missingokay if _rc==1 exit 1 else if _rc _symboli_err `PLOT' if `"``v''"'=="" _symboli_err `PLOT' } qui set obs `++n' foreach v of local xy { qui replace _C`v' = ``v'' in `n' } // ... [[*]size] ... gettoken size : 0, bind if (substr(`"`size'"',1,1)+substr(`"`size'"',-1,1))=="[]" { gettoken size 0 : 0, bind local size = substr(`"`size'"',2,strlen(`"`size'"')-2) if substr(`"`size'"',1,1)=="*" { local size = substr(`"`size'"',2,.) qui replace `RELSIZE' = 1 in `n' } capt confirm number `size' if _rc==1 exit 1 else if _rc _symboli_err `PLOT' "size" qui replace `SIZE' = (`size') in `n' } else { qui replace `SIZE' = `Size' in `n' if `Relsize' { qui replace `RELSIZE' = 1 in `n' } } if !`PLOT' continue // ... [(clockpos)] "label" local haslab 0 local haspos 0 gettoken pos : 0, qed(q) match(p) if "`p'"!="" { // ... (position) "label" gettoken pos 0 : 0, match(p) capt confirm integer number `pos' if _rc==1 exit 1 else if _rc _symboli_err `PLOT' "pos" if !inrange(`pos',0,12) _symboli_err `PLOT' "pos" gettoken lab : 0, qed(q) if !`q' local lab else gettoken lab 0 : 0 local haslab 1 local haspos 1 } else if `q' { // ... "label" gettoken lab 0 : 0 local pos . local haslab 1 } if `haslab' { qui replace POS = `pos' in `n' qui replace LAB = `"`lab'"' in `n' local HASLAB 1 if `haspos' local HASPOS 1 } } qui replace _ID = _n c_local haslab `HASLAB' c_local haspos `HASPOS' end program _symboli_err gettoken ml 0 : 0 gettoken err 0 : 0 di as err "invalid immediate value; the syntax for immediate values is" di as err " {it:#_x} {it:#_y} [{bf:[}[{bf:*}]{it:size}{bf:]}]" _c if `ml' di as err `" [{bf:(}{it:clockpos}{bf:)}] [{bf:"}{it:text}{bf:"}]"' else di as err "" if "`err'"=="pos" { di as err "{it:clockpos} must be an integer between 0 and 12" } else if "`err'"=="size" { di as err "{it:size} must be numeric" } exit 198 end program __geoplot_symbol gettoken layer 0 : 0 gettoken p 0 : 0 gettoken frame 0 : 0, pars(", ") frame `frame' { local sourcename: char _dta[GEOPLOT_sourcename] if `"`sourcename'"'=="" local sourcename `frame' // syntax syntax [anything] [if] [in] [iw/] [,/* */ SIze(str) MLabel(varname) MLABVposition(varname numeric)/* */ SELect(str asis) COLORVar(str) LEVels(str)/* */ COORdinates(varlist numeric min=2 max=2)/* */ CENTRoids(str) area(str)/* (will be ignored) */ _frameonly(str)/* undocumented; used by geoframe symbol */ * ] gettoken shparg : anything, match(paren) if "`paren'"=="(" { gettoken shparg anything : anything, match(paren) local options shparg(`shparg') `options' } _parse_zvar 0 varlist `anything' _parse_zvar 1 colorvar `colorvar' // mode (plot vs. data only) and name for tempframe local PLOT = `"`_frameonly'"'=="" if `PLOT' tempname frame1 else gettoken frame1 _frameonly : _frameonly local ORG local TGT // id and coordinates if !`PLOT' { // use existing id if called by geoframe geoframe get id, local(id) if `"`id'"'!="" { local ORG `ORG' `id' local TGT `TGT' _ID } } if "`coordinates'"=="" { geoframe get coordinates, strict local(coord) if `:list sizeof coord'>2 { mata: st_local("coord",/* */ invtokens(tokens(st_local("coord"))[|1\2|])) } else if `:list sizeof coord'<2 { di as err "wrong number of coordinate variables" exit 498 } } local ORG `ORG' `coord' local TGT `TGT' _CX _CY // parse size() tempname SIZE RELSIZE local ORG `ORG' `SIZE' `RELSIZE' local TGT `TGT' `SIZE' `RELSIZE' if `"`size'"'=="" { qui gen byte `SIZE' = 1 qui gen byte `RELSIZE' = 1 // relative size } else if substr(`"`size'"',1,1)=="*" { local size = substr(`"`size'"',2,.) if `"`size'"'=="" exit 198 qui gen double `SIZE' = (`size') qui gen byte `RELSIZE' = 1 // relative size } else { qui gen double `SIZE' = (`size') qui gen byte `RELSIZE' = . // absolute size } // collect variables from plot options if `PLOT' { // zvar if `"`colorvar'"'!="" local zvar `colorvar' else local zvar `varlist' if "`zvar'"!="" { if substr("`zvar'",1,2)=="i." { // i.zvar local zvar = substr("`zvar'",3,.) local FV "i." } local ZVAR ZVAR local ORG `ORG' `zvar' local TGT `TGT' `ZVAR' // get variable from levels(, weight()) if `"`levels'"'!="" { _parse_levels `levels' // (inserts lW) local options levels(`levels') `options' if "`lwvar'"!="" { local ORG `ORG' `lwvar' local TGT `TGT' lW } } } // mlabels if "`mlabel'"!="" { local ORG `ORG' `mlabel' local TGT `TGT' LAB local mlabel mlabel(LAB) if "`mlabvposition'"!="" { local ORG `ORG' `mlabvposition' local TGT `TGT' POS local mlabvposition mlabvposition(POS) } } // weights if "`weight'"!="" { tempname wvar qui gen double `wvar' = abs(`exp') local wgt [iw=`wvar'] local ORG `ORG' `wvar' local TGT `TGT' `wvar' } // select if `"`select'"'!="" { tempvar SELECT qui gen byte `SELECT' = 1 qui replace `SELECT' = 0 if !(`select') local ORG `ORG' `SELECT' local TGT `TGT' `SELECT' local options select(`SELECT'==1) `options' } } // sample marksample touse, novarlist markout `touse' `coord' `wvar' `SIZE' } // create attribute frame frame create `frame1' frame `frame1' { qui geoframe append `frame' `ORG', target(`TGT') touse(`touse') fast if "`id'"=="" { qui gen byte _ID = . qui replace _ID = _n order _ID } char _dta[GEOPLOT_sourcename] `sourcename' } // create shape frame and plot command ___geoplot_symbol `layer' `p' `frame1' "`FV'`ZVAR'" "`wgt'",/* */ size(`SIZE' `RELSIZE') _frameonly(`_frameonly')/* */ `mlabel' `mlabvposition' `options' if !`PLOT' exit c_local plot `plot' c_local p `p' end program _parse_zvar // removes "i." from "i.zvar" if zvar is string gettoken opt 0 : 0 gettoken lnm 0 : 0 // nothing to do if empty local l: list sizeof 0 if `l'==0 { c_local `lnm' exit } // must be single token if `l'>1 { if `opt' di as err "`lnm'(): " _c di as err "too many variables specified" exit 103 } gettoken 0 : 0 // strip leading space // strip "i." if string if substr(`"`0'"',1,2)=="i." { local 00: copy local 0 local 0 = substr(`"`0'"',3,.) capt syntax varname(str) if _rc==1 exit 1 if _rc local 0: copy local 00 } // parse varlist if `opt' { local 0 `", `lnm'(`0')"' syntax, `lnm'(varname fv) c_local `lnm' ``lnm'' exit } syntax varname(fv) c_local `lnm' `varlist' end program _parse_levels _parse comma lhs 0 : 0 syntax [, Weight(varname numeric) * ] if "`weight'"!="" { local options weight(lW) `options' c_local levels `"`lhs', `options'"' c_local lwvar `weight' } end program ___geoplot_symbol _parse comma lhs 0 : 0 gettoken layer lhs : lhs gettoken p lhs : lhs gettoken frame lhs : lhs gettoken zvar lhs : lhs gettoken wgt lhs : lhs local mlopts MLabel MLABSTYle MLABPosition MLABVposition MLABGap MLABANGle/* */ MLABTextstyle MLABSize MLABColor MLABFormat MLABJUSTification/* */ mlabalign foreach opt of local mlopts { local MLOPTS `MLOPTS' `opt'(passthru) } local mlopts = strlower("`mlopts'") syntax [, size(str) _frameonly(str) shparg(str asis)/* */ tsize(numlist max=1 >=0)/* (undocumented; fix text size) */ SHape(passthru) n(passthru) OFFset(numlist max=2)/* */ ANGle(numlist max=1) ratio(numlist max=1) slant(numlist max=1)/* */ align(str) line SELect(str asis) `MLOPTS' * ] if `"`select'"'!="" { local options select(`select') `options' local select `" if (`select')"' } // size(), _frameonly(), shparg() gettoken size relsize : size gettoken relsize : relsize local PLOT = `"`_frameonly'"'=="" if `PLOT' tempname frame1 else local frame1 `_frameonly' if `"`shape'"'=="" { if `"`shparg'"'!="" local shape shape(`shparg') } // parse symbol options: size(), shape(), n(), offset(), align()... if `layer'<. { local SHAPE `shape' `n' if "`offset'"!="" local SHAPE `SHAPE' offset(`offset') if "`angle'"!="" local SHAPE `SHAPE' angle(`angle') if "`ratio'"!="" local SHAPE `SHAPE' ratio(`ratio') if "`slant'"!="" local SHAPE `SHAPE' slant(`slant') if "`align'"!="" local SHAPE `SHAPE' align(`align') } _parse_shape, `n' `shape' // returns shape, arg, n if "`shape'"=="@label" { if !`PLOT' { di as err `"{bf:shape("}{it:text}{bf:")}"'/* */ " not allowed with {bf:geoframe symbol}" exit 198 } if `"`wgt'"'!="" { di as err `"weights not allowed with {bf:shape("}{it:text}{bf:")}"' exit 101 } } gettoken offset oangle : offset gettoken oangle: oangle if "`offset'"=="" local offset 0 if "`oangle'"=="" local oangle 0 if "`angle'"=="" local angle 0 if "`ratio'"=="" local ratio 1 if "`slant'"=="" local slant 0 _parse_align, `align' // plottype if "`line'"!="" local plottype line else local plottype area // get range of underlying map (including positions of symbols) frame `frame' { su _CX`select', meanonly local xmin = r(min) local xmax = r(max) su _CY`select', meanonly local ymin = r(min) local ymax = r(max) } if `PLOT' { su X, meanonly local xmin = min(`xmin', r(min)) local xmax = max(`xmax', r(max)) su Y, meanonly local ymin = min(`ymin', r(min)) local ymax = max(`ymax', r(max)) } local REFSIZE = min(`xmax'-`xmin', `ymax'-`ymin') // write shapes to shape frame if "`shape'"!="@label" frame create `frame1' frame `frame' { tempname SIZE mata: _compute_symbols("`frame1'", "`shape'", st_local("arg"),/* */ `n', `angle', `ratio', `slant', "`size'", "`relsize'",/* */ `REFSIZE', `offset', `oangle', "`align_lr'", "`align_bt'") } if !`PLOT' exit // generate plot if "`shape'"=="@label" { if "`align_lr'"=="left" { if "`align_bt'"=="top" local pos 5 else if "`align_bt'"=="bottom" local pos 1 else local pos 3 } else if "`align_lr'"=="right" { if "`align_bt'"=="top" local pos 7 else if "`align_bt'"=="bottom" local pos 11 else local pos 9 } else { if "`align_bt'"=="top" local pos 6 else if "`align_bt'"=="bottom" local pos 12 else local pos 0 } if "`tsize'"=="" { local tsize = `SIZE' / `REFSIZE' * 200 if (`tsize'>=.) local tsize 3 } if `angle' local angle angle(`angle') else local angle _geoplot_label `layer' `p' `frame' (`"`arg'"') `zvar', /* */ size(`tsize') gap(0) position(`pos') `angle' `options' } else { local tsize frame `frame': qui geoframe link `frame1' __geoplot_layer `plottype' `layer' `p' `frame' `zvar' `wgt',/* */ lforce lock `options' } if "`mlabel'"!="" { local PLOT: copy local plot if "`mlabposition'"=="" local mlabposition mlabposition(0) local MLOPTS foreach opt of local mlopts { local MLOPTS `MLOPTS' ``opt'' } local n0 = _N + 1 __geoplot_layer scatter . `p' `frame' `zvar',/* */ msymbol(i) msize(0) `MLOPTS' `options' qui replace LAYER = `layer' in `n0'/l local plot `PLOT' `plot' } c_local plot `plot' c_local p `p' if `layer'<. { char LAYER[symbol_`layer'] `SHAPE' char LAYER[symsize_`layer'] `=`SIZE'' char LAYER[symtsize_`layer'] `tsize' // size for shape("text") char LAYER[layertype_`layer'] symbol } end program _parse_shape syntax [, n(numlist int max=1 >0) SHape(str asis) ] if "`n'"=="" local n . // shape(numlist) capt numlist `"`shape'"' if _rc==1 exit 1 if _rc==0 { c_local shape @numlist c_local arg `r(numlist)' c_local n . exit } // label (text symbol) gettoken shape arg : shape, qed(label) local arg = strtrim(`"`arg'"') if `label' { c_local shape "@label" c_local arg `"`shape'"' // ignoring arg c_local n . exit } // predefined shapes local SHAPES0 Triangle Square Pentagon HEXagon HEPtagon Octagon local shapes0 = strlower("`SHAPES0'") local SHAPES `SHAPES0' Circle Arc SLice star star6 PENTAGRam HEXAGRam pin/* */ Line pipe PLus x DIamond v ARRow FARRow BARRow FBARRow bar/* */ CRoss ASTerisk FASTerisk TRApezoid pin2 pin3 local shapes = strlower("`SHAPES'") local 0 , `shape' capt syntax [, `SHAPES' ] if _rc==1 exit 1 if _rc==0 { local shape foreach s of local shapes { if "``s''"!="" { local shape `s' continue, break } } if "`shape'"=="" local shape circle // default else if `: list shape in shapes0' { local n: list posof "`shape'" in shapes local n = `n' + 2 local shape circle } c_local shape `shape' c_local arg `"`arg'"' c_local n `n' exit } // matrix capt confirm matrix `shape' if _rc==1 exit 1 if _rc==0 { c_local shape @matrix c_local arg `shape' c_local n . exit } // mata function (potentially) capt confirm name _`shape' if _rc==1 exit 1 if _rc { di as err "invalid specification in shape()" exit 198 } c_local shape `shape' c_local arg `"`arg'"' c_local n `n' end program _parse_align syntax [, Left Right Bottom Top Center ] local lr `left' `right' if `: list sizeof lr'>1 { di as err "only one of {bf:left} and {bf:right} allowed in {bf:align()}" exit 198 } if "`lr'"=="" local lr `center' local bt `bottom' `top' if `: list sizeof bt'>1 { di as err "only one of {bf:bottom} and {bf:top} allowed in {bf:align()}" exit 198 } if "`bt'"=="" local bt `center' c_local align_lr `lr' c_local align_bt `bt' end version 16.1 mata: mata set matastrict on void _compute_symbols(string scalar frame, string scalar shape, string scalar arg, real scalar n, real scalar angle, real scalar ratio, real scalar slant, string scalar SIZE, string scalar RELSIZE, real scalar refsize, real scalar off, real scalar oang, string scalar align_lr, string scalar align_bt) { real scalar i, a, b, s, haspl real matrix XY, xy, V real colvector ID, pl, PL, S // get origin data st_view(XY=., ., "_CX _CY") // determine size if (refsize) s = refsize * 0.015 // default size: 1.5% of refsize else s = 1 // or 1 if refsize is zero S = st_data(., SIZE) :* editmissing(st_data(., RELSIZE)*s, 1) st_numscalar(st_local("SIZE"), mean(S)) // apply offset to centroids (and write back to data) if (off) XY[.,.] = XY :+ S :* J(rows(S), 1, (off/100)*(cos(oang*pi()/180), sin(oang*pi()/180))) if (shape=="@label") return // done (no shapes needed) // create shape haspl = 0 if (shape=="@numlist") { xy = strtoreal(tokens(arg)) n = length(xy) / 2 if (n!=trunc(n)) { errprintf("shape({it:numlist}): even number of values required\n") exit(503) } xy = colshape(xy, 2) } else if (shape=="@matrix") { xy = st_matrix(arg) n = length(xy) / 2 if (n!=trunc(n)) { errprintf("shape({it:matname}): even number of values required\n") exit(503) } if (cols(xy)!=2) xy = colshape(xy', 2) } else { xy = geo_symbol(shape, n, arg) if (cols(xy)>2) { haspl = 1 pl = xy[,3] pl = pl[1] \ pl // duplicate 1st element xy = xy[,(1,2)] } } n = rows(xy) // align if (align_lr=="left") xy[,1] = xy[,1] :- min(xy[,1]) else if (align_lr=="right") xy[,1] = xy[,1] :- max(xy[,1]) else if (align_lr=="center") xy[,1] = xy[,1] :- sum(minmax(xy[,1]))/2 if (align_bt=="bottom") xy[,2] = xy[,2] :- min(xy[,2]) else if (align_bt=="top") xy[,2] = xy[,2] :- max(xy[,2]) else if (align_bt=="center") xy[,2] = xy[,2] :- sum(minmax(xy[,2]))/2 // apply ratio, slant, and angle if (ratio!=1) xy[,2] = ratio * xy[,2] if (slant) xy[,1] = xy[,1] + slant * xy[,2] _geo_rotate(xy, angle) // prepare destination data ID = st_data(., "_ID") i = rows(XY) st_framecurrent(frame) st_addobs(i * (n+1)) st_view(V=., ., st_addvar("double", tokens("_ID _X _Y"))) if (haspl) st_view(PL=., ., st_addvar("byte", "_PLEVEL")) // write shapes to destination data a = i * (n+1) + 1 for (;i;i--) { b = a - 1 a = b - n V[|a,1 \ b,.|] = J(n+1, 1, ID[i]), (J(1,2,.) \ (XY[i,] :+ xy*S[i])) if (haspl) PL[|a \ b|] = pl } } end