*! version 2.0.3 27jun2026 Ben Jann program _mklegend, rclass version 14 _on_colon_parse `0' local 0 `"`s(before)'"' // parse s(after) mata: keys_expand() // returns key_n, key_# // parse [graphname] [numlist] _parse comma subgr 0 : 0 gettoken graph : subgr capt confirm name `graph' if _rc==0 gettoken graph subgr : subgr else local graph if `"`subgr'"'!="" { numlist `"`subgr'"', integer range(>=1) local subgr `r(numlist)' } // parse options syntax [, nodraw Margin(passthru) /* */ FRame FRame2(str) lskip(real 1.5)/* */ y(real 95) x(real 5) h(real 5) w(real 5)/* */ ty(real 0) tx(numlist max=1) tw(real 20) Text(str)/* */ PSTYle(passthru) * ] mata: extract_opts(("Y", "X", "H", "W", "TY", "TX", "TW")) foreach opt in Y X H W TY TX TW { if "``opt''"!="" numlist `"``opt''"', max(1) } foreach opt in Y X H W tx TY TX TW { if "``opt''"=="" local `opt' . } parse_topts, `text' // returns place, just, topts if `"`frame'`frame2'"'!="" { local frame frame parse_frame, `frame2' } // get limits of plotregion from graph parse_graph "`graph'" "`subgr'" // => graph subgr grfam Ymin Ymax Xmin Xmax local Yr = (`Ymax' - `Ymin') local Xr = (`Xmax' - `Xmin') if `Y'>=. local Y = `Ymin' + `y' * `Yr' / 100 else local y = (`Y' - `Ymin') * 100 / `Yr' if `X'>=. local X = `Xmin' + `x' * `Xr' / 100 else local x = (`X' - `Xmin') * 100 / `Xr' if `H'>=. local H = `h' * `Yr' / 100 else local h = `H' * 100 / `Yr' if `W'>=. local W = `w' * `Xr' / 100 else local w = `W' * 100 / `Xr' if `TY'>=. local TY = `ty' * `Yr' / 100 else local ty = `TY' * 100 / `Yr' if `TX'>=. local TX = `tx' * `Xr' / 100 else local tx = `TY' * 100 / `Xr' if `TW'>=. local TW = `tw' * `Xr' / 100 else local tw = `TW' * 100 / `Xr' // global settings foreach opt in Xmax Xmin Ymax Ymin lskip TW TX TY W H X Y tw tx ty w h x y { return scalar `opt' = ``opt'' } // create keys local minY `Y' local maxY `Y' local minX `X' local maxX `X' local p 0 forv i=1/`key_n' { parse_key `minY' `maxY' `minX' `maxX'/* */ `p' `Ymin' `Yr' `Xmin' `Xr' `Y' `X' `H' `W' `"`pstyle'"'/* */`"`options'"' `TY' `TX' `TW' `"`place'"' `"`just'"' `"`topts'"'/* */ `key_`i'' // returns plots, p, Y, X, H, W, TY, ... local legend `legend' `plots' local Y = `Y' - `lskip' * `H' } // create frame if "`frame'"!="" { if `fr_YM'>=. local fr_YM = `fr_ym' * `Yr' / 100 else local fr_xm = `fr_YM' * 100 / `Yr' if `fr_XM'>=. local fr_XM = `fr_xm' * `Xr' / 100 else local fr_xm = `fr_XM' * 100 / `Xr' local fr_B = cond(`Yr'<0,`maxY',`minY') - `fr_YM' local fr_T = cond(`Yr'<0,`minY',`maxY') + `fr_YM' local fr_L = cond(`Xr'<0,`maxX',`minX') - `fr_XM' local fr_R = cond(`Xr'<0,`minX',`maxX') + `fr_XM' if `fr_Y'>=. { if `fr_y'<. local fr_Y = `Ymin' + `fr_y' * `Yr' / 100 else local fr_Y = `fr_T' } if `fr_y'>=. local fr_y = (`fr_Y' - `Ymin') / `Yr' * 100 if `fr_H'>=. { if `fr_h'<. local fr_H = `fr_y' * `Yr' / 100 else local fr_H = `fr_Y' - `fr_B' } if `fr_h'>=. local fr_h = `fr_H' / `Yr' * 100 if `fr_X'>=. { if `fr_x'<. local fr_X = `Xmin' + `fr_x' * `Xr' / 100 else local fr_X = `fr_L' } if `fr_x'>=. local fr_x = (`fr_X' - `Xmin') / `Xr' * 100 if `fr_W'>=. { if `fr_w'<. local fr_W = `fr_w' * `Xr' / 100 else local fr_W = `fr_R' - `fr_X' } if `fr_w'>=. local fr_w = `fr_W' / `Xr' * 100 local fr_B `: di %10.0g `fr_Y' - `fr_H'' local fr_T `: di %10.0g `fr_Y'' local fr_L `: di %10.0g `fr_X'' local fr_R `: di %10.0g `fr_X' + `fr_W'' local legend (scatteri `fr_B' `fr_L' `fr_B' `fr_R' `fr_T' `fr_R'/* */ `fr_T' `fr_L', recast(area) nodropbase `fr_opts') `legend' foreach opt in XM YM W H X Y xm ym w h x y { return scalar fr_`opt' = `fr_`opt'' } } // return result return local graphfamily `"`grfam'"' return local subgraphs `"`subgr'"' return local graphname `"`graph'"' return local legend `"`legend'"' end program parse_topts syntax [, PLACEment(str) Justification(str) * ] c_local place `"`placement'"' c_local just `"`justification'"' c_local topts `options' end program parse_graph gettoken graph 0 : 0 gettoken plots 0 : 0 gettoken plot : plots if `"`graph'"'=="" { local graph `"`._Gr_Global.current_graph'"' if `"`graph'"'=="" { // no graph available di as txt "(no graph found; assuming range 0 to 100 for both axes)" c_local graph "" c_local subgr "" c_local grfam "" c_local Ymin 0 c_local Ymax 100 c_local Xmin 0 c_local Xmax 100 exit } } // obtain defaults from graph capt classutil d `graph' if _rc { di as err `"graph `graph' not found"' exit 111 } if "`plot'"!="" { capt classutil d `graph'.graphs[`plot'] if _rc { di as err `"subgraph `plot' not found"' exit 111 } local grtype `"`.`graph'.graphs[`plot'].graphfamily'"' if `"`grtype'"'!="twoway" { di as err `"subgraph `plot' not twoway"' exit 498 } } else { local grtype `"`.`graph'.graphfamily'"' if `"`grtype'"'!="twoway" { local plots local nplots `"`.`graph'.n'"' capt confirm integer number `nplots' if _rc==0 { forv plot = 1/`nplots' { if `"`.`graph'.graphs[`plot'].graphfamily'"'=="twoway" { local plots `plots' `plot' } } } if "`plots'"=="" { di as err "no twoway subgraphs found in `graph'" exit 111 } gettoken plot : plots } } if "`plot'"=="" local Graph `graph' else local Graph `graph'.graphs[`plot'] foreach Z in Y X { local z = strlower("`Z'") local `z'rev `.`Graph'.plotregion1.`z'scale.reverse.istrue' if `"``z'rev'"'=="1" { c_local `Z'min `.`Graph'.plotregion1.`z'scale.curmax' c_local `Z'max `.`Graph'.plotregion1.`z'scale.curmin' } else { c_local `Z'min `.`Graph'.plotregion1.`z'scale.curmin' c_local `Z'max `.`Graph'.plotregion1.`z'scale.curmax' } } c_local graph "`graph'" c_local subgr "`plots'" c_local grfam "`.`graph'.graphfamily'" end program parse_frame syntax [, y(numlist max=1) x(numlist max=1)/* */ h(numlist max=1) w(numlist max=1)/* */ ym(real 2.5) xm(real 2)/* */ LSTYle(passthru) FColor(passthru) * ] mata: extract_opts(("Y", "X", "H", "W", "YM", "XM")) foreach opt in Y X H W YM XM { if "``opt''"!="" numlist `"``opt''"', max(1) } foreach opt in y x h w Y X H W YM XM { if "``opt''"=="" local `opt' . } foreach opt in y x h w ym xm Y X H W YM XM { c_local fr_`opt' `"``opt''"' } if `"`lstyle'"'=="" local lstyle lstyle(foreground) if `"`fcolor'"'=="" local fcolor fcolor(white) c_local fr_opts `lstyle' `fcolor' `options' end program parse_key // options foreach arg in minY maxY minX maxX p Ymin Yr Xmin Xr _Y _X _H _W/* */ _pstyle _options _TY _TX _TW _place _just _topts { gettoken `arg' 0 : 0 } _parse comma txt 0 : 0 syntax [, /* */ y(numlist max=1) x(numlist max=1)/* */ h(numlist max=1) w(numlist max=1)/* */ ty(numlist max=1) tx(numlist max=1) tw(numlist max=1) Text(str)/* */ PSTYle(passthru) * ] if "`y'"!="" local _Y = `Ymin' + `y' * `Yr' / 100 if "`x'"!="" local _X = `Xmin' + `x' * `Xr' / 100 if "`h'"!="" local _H = `h' * `Yr' / 100 if "`w'"!="" local _W = `w' * `Xr' / 100 if "`ty'"!="" local _TY = `ty' * `Yr' / 100 if "`tx'"!="" local _TX = `tx' * `Xr' / 100 if "`tx'"!="" local _TW = `tw' * `Xr' / 100 mata: extract_opts(("Y", "X", "H", "W", "TY", "TX", "TW")) foreach opt in Y X H W TY TX TW { if "``opt''"!="" numlist `"``opt''"', max(1) } parse_topts, `text' // returns place, just, topts foreach opt in Y X H W TY TX TW pstyle place just { if "``opt''"!="" continue local `opt' `"`_`opt''"' } local options `_options' `options' local topts `_topts' `topts' // collect symbols gettoken next : txt if inlist(`"`next'"', ".", "-") { gettoken next txt : txt local sym_n 0 local hassym = `"`next'"'=="-" } else { local i 0 while (1) { gettoken next : txt, match(par) if `"`par'"'!="" { local ++i gettoken sym_`i' txt : txt, match(par) continue } continue, break } local sym_n `i' if `sym_n' { local hassym 1 local p = mod(`++p'-1, 15) + 1 } else local hassym 0 } // plot symbols local plots forv i=1/`sym_n' { parse_sym `p' `Ymin' `Yr' `Xmin' `Xr' `Y' `X' `H' `W'/* */ `"`pstyle'"' `"`options'"' `sym_`i'' // return plot local plots `plots' `plot' } // collect label parse_txt `txt' // returns txt // plot label if `TX'>=. local tx = 0.75 * `W' else local tx `TX' if (sign(`tx')*sign(`Xr'))<0 { if `"`place'"'=="" local place left if `"`just'"'=="" local just right local tdir -1 } else { if `"`place'"'=="" local place right if `"`just'"'=="" local just left local tdir 1 } if `hassym' { local ty = `Y' + `TY' local tx = `X' + `tx' } else { local ty = `Y' local tx = `X' - 0.5*`W' } local y `: di %10.0g `ty'' local x `: di %10.0g `tx'' local topts place(`place') just(`just') `topts' local plots `plots' (scatteri `y' `x', ms(i) text(`y' `x' `txt', `topts')) // update range of legend (ignoring individual symbol settings) local tmp "`Y'-0.5*`H', `Y'+0.5*`H', `ty'-0.5*`H', `ty'+0.5*`H'" local minY = min(`minY', `tmp') local maxY = max(`maxY', `tmp') local tmp "`X'-0.5*`W', `X'+0.5*`W', `tx', `tx'+`tdir'*`TW'" local minX = min(`minX', `tmp') local maxX = max(`maxX', `tmp') // returns foreach opt in p Y X H W TY TX TW minY maxY minX maxX plots { c_local `opt' ``opt'' } end program parse_sym // options foreach arg in p Ymin Yr Xmin Xr _Y _X _H _W _pstyle _options { gettoken `arg' 0 : 0 } _parse comma SYM 0 : 0 syntax [, y(numlist max=1) x(numlist max=1)/* */ h(numlist max=1) w(numlist max=1)/* */ PSTYle(passthru) * ] if "`h'"!="" local _H = `h' * `Yr' / 100 if "`w'"!="" local _W = `w' * `Xr' / 100 mata: extract_opts(("Y", "X", "H", "W")) foreach opt in Y X H W { if "``opt''"!="" numlist `"``opt''"', max(1) } foreach opt in H W pstyle { if "``opt''"!="" continue local `opt' `"`_`opt''"' } if "`Y'"=="" { if "`y'"!="" local Y = `y' * `Yr' / 100 else local Y 0 } local Y = `_Y' + `Y' if "`X'"=="" { if "`x'"!="" local X = `x' * `Xr' / 100 else local X 0 } local X = `_X' + `X' local options `_options' `options' parse_getpnum, `pstyle' // updates p parse_getopt MLABPosition, `options' parse_getopt LSTYle, `options' if `"`lstyle'"'!="" local lstyle else local lstyle lstyle(p`p'other) // used by spike, cap, capsym // plot symbols if !`: list sizeof SYM' local SYM "." local plots local b `: di %10.0g `Y' - 0.5 * `H'' local t `: di %10.0g `Y' + 0.5 * `H'' local m `: di %10.0g `Y'' local l `: di %10.0g `X' - 0.5 * `W'' local r `: di %10.0g `X' + 0.5 * `W'' local c `: di %10.0g `X'' while (`"`SYM'"'!="") { local ptype local LTSYLE gettoken sym SYM : SYM, qed(istext) if `istext' { if `"`mlabposition'"'=="" local plot (0) else local plot local plot scatteri `m' `c' `plot' `"`sym'"', ms(i) } else { if `"`sym'"'=="rcap" local sym "cap" else if `"`sym'"'=="rcapsym" local sym "capsym" if inlist(`"`sym'"',"line","vline") { if "`sym'"=="line" local plot `m' `l' `m' `r' else local plot `b' `c' `t' `c' local plot scatteri `plot', recast(line) } else if inlist(`"`sym'"',"spike","vspike") { if "`sym'"=="spike" local plot `m' `l' `m' `r' else local plot `b' `c' `t' `c' local plot scatteri `plot', recast(line) local LSTYLE `lstyle' } else if inlist(`"`sym'"',"rline","vrline") { if "`sym'"=="rline" local plot `t' `l' `t' `r' . . `b' `l' `b' `r' else local plot `b' `l' `t' `l' . . `b' `r' `t' `r' local plot scatteri `plot', recast(line) cmissing(n) } else if inlist(`"`sym'"',"area") { local ptype "area" local plot `b' `l' `b' `r' `t' `r' `t' `l' local plot scatteri `plot', recast(area) nodropbase } else if inlist(`"`sym'"',"bar") { local ptype "bar" local plot `b' `l' `b' `r' `t' `r' `t' `l' local plot scatteri `plot', recast(area) nodropbase } else if inlist(`"`sym'"',"cap", "vcap") { if "`sym'"=="cap" local plot `m' `l' `m' `r', ms(|) else local plot `b' `c' `t' `c', ms(|) msang(90) local plot scatteri `plot' recast(connect) local LSTYLE `lstyle' } else if inlist(`"`sym'"',"capsym", "vcapsym") { if "`sym'"=="capsym" local plot `m' `l' `m' `r' else local plot `b' `c' `t' `c' local plot scatteri `plot', recast(connect) local LSTYLE `lstyle' } else { local plot ms(`sym') if `"`sym'"'=="." local plot local plot scatteri `m' `c', `plot' } } if `"`pstyle'"'=="" local PSTYLE pstyle(p`p'`ptype') `LSTYLE' else local PSTYLE `pstyle' `LSTYLE' local OPTIONS `PSTYLE' `options' local plots `plots' (`plot' `OPTIONS') } // returns c_local plot `plots' end program parse_getpnum syntax [, PSTYle(str) ] if regexm(`"`pstyle'"', "^p([1-9][0-9]*)") { c_local p = regexs(1) } end program parse_getopt _parse comma OPT 0 : 0 syntax [, `OPT'(passthru) * ] local opt = strlower("`OPT'") c_local `opt' `"``opt''"' end program parse_txt c_local txt `"`0'"' while (`"`0'"'!="") { gettoken tok 0 : 0, qed(qed) if `qed' continue di as err `"{bf:`tok'} found where quoted text expected"' exit 198 } end version 14 mata: mata set matastrict on void extract_opts(string rowvector O) { string scalar opt, arg, options transmorphic t t = tokeninit(" ", "", "()") tokenset(t, st_local("options")) options = "" while ((opt = tokenget(t))!="") { arg = tokenpeek(t) if (_token_has_pars(arg)) { // option has arguments (void) tokenget(t) if (anyof(O, opt)) { // match found; extract option st_local(opt, strtrim(substr(arg, 2, strlen(arg)-2))) continue } options = options + " " + opt + arg continue } options = options + " " + opt } st_local("options", substr(options, 2, .)) } real scalar _token_has_pars(string scalar s) { // 1 if "(...)", 0 else if (substr(s,1,1)=="(") { if (substr(s,-1,1)==")") return(1) return(0) } return(0) } void keys_expand() { real scalar i, a, b, par, opts, newkey string scalar s, tok transmorphic t s = st_global("s(after)") t = tokeninit(" ", (",", "||"), (`""""', `"`""'"', "()")) tokenset(t, s) newkey = opts = par = i = 0 b = 1 while ((tok = tokenget(t))!="") { if (_token_has_pars(tok)) { if (opts==par) newkey = 1 /* start new key it token is "(...)" and one of the following cases applies: (1) the position of the token is not in a section of options and the token is not preceded by "(...)" (2) the position of the token is in a section of options and it is preceded by "(...)" */ par = 1 // remember that token is "(...)" for the next loop } else { if (tok==".") newkey = 1 // heading else if (tok=="-") newkey = 1 // subheading else if (tok==",") { if (opts) newkey = 1 // repeated set of options else opts = 1 // start of options, i.e. ", ..." } else if (tok=="||") newkey = 2 // explicit key delimiter par = 0 // remember that token is not "(...)" for the next loop } if (newkey) { a = b; b = tokenoffset(t) opts = 0 if (newkey==2) { // (explicit delimiter) _keys_expand(i, s, a, b-a-2) // extract key (w/o delimiter) tok = tokenpeek(t) if (tok!="||") { // move to first token after delimiter tok = tokenget(t) if (_token_has_pars(tok)) par = 1 // token is "(...)" else if (tok==",") opts = 1 // start of options } } else { // (implicit delimiter) b = b - strlen(tok) _keys_expand(i, s, a, b-a) // extract key } newkey = 0 } } _keys_expand(i, s, b, .) // extract last key st_local("key_n", strofreal(i)) } void _keys_expand(real scalar i, string scalar s, real scalar a, real scalar l) { string scalar tok tok = strtrim(substr(s, a, l)) if (tok!="") { i++ st_local("key_"+strofreal(i), tok) } } end