/*--------------Test Program-------------------------- * Basic BY-group example; data x; do b = 1 to 2; do x = 0.1 to 10 by 0.1; y = log(x) + sin(b * x) + normal(104); output; end; end; run; %macro mygraph; proc transreg data=__bydata; model ide(y) = pbs(x); %mend; ods graphics on; ods html body='b.html'; %modtmplt(by=b, data=x, template=stat.transreg) ods html close; ------------------------------------------------------------------------------- * An example of using a system title; ods graphics on; title 'Spline Fit By Sex'; %modtmplt(template=Stat.transreg, options=replace, steps=t) ods html body='b.html'; proc transreg data=sashelp.class; model ide(weight) = class(sex / zero=none) * spline(height); run; ods html close; %modtmplt(template=Stat.transreg, steps=d) ------------------------------------------------------------------------------- * The following test puts the ModTmplt macro through its paces with some gnarly n-lit variable names and silly BY values.; options validvarname=any; data x; do b1 = 1; do b2 = 2, 3; do '"b" ''3'''n = '%if'; do b4 = 6; do x = 0.1 to 10 by .1; y = log(x) + sin(x) + normal(7); output; end; end; end; end; end; format b1 5.3 b2 words8.0; run; proc sort; by '"b" ''3'''n b1 b4 b2; run; * macro looks for this macro to contain the SAS program. Does not have BY or RUN statement; %macro mygraph; proc transreg data=__bydata plots=all; model ide(y) = pbs(x); %mend; ods graphics on; ods html body='b.html'; title 'first title'; %modtmplt(template=Stat.transreg, by='"b" ''3'''n b1 b4 b2 , data=x, statement=entrytitle, options=first) title 'last title'; %modtmplt(template=Stat.transreg, by='"b" ''3'''n b1 b4 b2 , data=x, statement=entrytitle) title 'default (footnote and left aligned)'; %modtmplt(template=Stat.transreg, by='"b" ''3'''n b1 b4 b2 , data=x) title 'centered and bolder footnote'; %modtmplt(template=Stat.transreg, by='"b" ''3'''n b1 b4 b2 , data=x, statement=entryfootnote textattrs=GraphLabelText) ods html close; ----------------------------------TEST PROGRAM-------------------------------*/ /*----------------------------------------------------------------------------- DISCLAIMER: THIS INFORMATION IS PROVIDED BY SAS INSTITUTE INC. AS A SERVICE TO ITS USERS. IT IS PROVIDED "AS IS". THERE ARE NO WARRANTIES, EXPRESSED OR IMPLIED, AS TO MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE REGARDING THE ACCURACY OF THE MATERIALS OR CODE CONTAINED HEREIN. -----------------------------------------------------------------------------*/ %macro modtmplt( /*-------------------------------------*/ /* Use the ModTmplt macro to modify */ /* ODS Graphics templates. You can */ /* insert BY line information, titles, */ /* and footnotes in ODS Graphics and */ /* make other template changes. */ /* */ /* The ModTmplt macro has several uses.*/ /* When you use it to insert BY lines, */ /* it requires you to construct a SAS */ /* macro called MyGraph that contains */ /* the SAS procedure that needs to be */ /* run. Here is an example: */ /* */ /* %macro mygraph; */ /* proc transreg data=__bydata; */ /* model ide(y) = pbs(x); */ /* %mend; */ /* */ /* %modtmplt(by=b1-b3, data=x, */ /* template=Stat.Transreg.Graphics) */ /* */ /* Notice that the BY and RUN */ /* statements are not specified in the */ /* MyGraph macro. Also notice that */ /* you must use DATA=__BYDATA with */ /* your procedure and specify the real */ /* input data set on the DATA= option */ /* of the ModTmplt macro. */ /* */ /* The ModTmplt macro outputs the */ /* specified template(s) to a file, */ /* adds either an EntryTitle or */ /* EntryFootNote statement that adds */ /* the by line information, then runs */ /* the MyGraph macro once for each BY */ /* group. In the end, it deletes the */ /* modified template. See the STEPS= */ /* option if you do not wish to have */ /* all of these steps performed. */ /* */ data=_last_, /* specifies the input SAS data set. */ /* */ titles=, /* specifies the data set with titles. */ /* By default when the system titles */ /* are used, PROC SQL is used to */ /* determine the titles. You can */ /* instead create this data set */ /* yourself so that you can set the */ /* graph titles independently from the */ /* system titles. The data set must */ /* contain two variables: TYPE */ /* (TYPE='T' for titles and TYPE='F' */ /* for footnotes), and TEXT, which */ /* contains the titles and footnotes. */ /* Other variables are ignored. */ /* */ template=, /* specifies the name of the */ /* template(s) to modify. You can */ /* specify just the first few levels */ /* to modify a series of templates. */ /* For example, to modify all of PROC */ /* REG's graph templates, specify: */ /* TEMPLATE=stat.reg.graphics, */ /* */ file="template.txt", /* specifies the file to store the */ /* original template(s). This is a */ /* work file. You can either specify a */ /* quoted file name or the name from a */ /* FILENAME statement that you */ /* provide. */ /* */ statement=entryfootnote halign=left /* specifies the statement to add to */ textattrs=graphvaluetext, /* the template along with any */ /* statement options. This is the */ /* statement that is used to provide */ /* the BY line information. Examples: */ /* STATEMENT=entrytitle, */ /* STATEMENT=entryfootnote halign=left */ /* textattrs=GraphLabelText, */ /* */ titleopts=, /* Options that are used for all for */ /* system titles and footnotes. You */ /* can specify, for example, HALIGN= */ /* and TEXTATTRS= options like in the */ /* STATEMENT= option. With OPTIONS= */ /* NOQUOTES, you can specify options */ /* individually. */ /* */ /* You can use the following 10 */ /* options to add or replace options */ /* on up to 10 selected statements. */ /* Here is an example: */ /* */ /* stmtopts1=2 add densityplot */ /* legendlabel='Kernel Density', */ /* */ stmtopts1=, /* These options require you to */ stmtopts2=, /* specify a series of values. The */ stmtopts3=, /* first value is the statement */ stmtopts4=, /* number. The second value is: ADD, */ stmtopts5=, /* REPLACE, DELETE, BEFORE, or AFTER. */ stmtopts6=, /* When the second value is ADD or */ stmtopts7=, /* REPLACE, it controls whether you */ stmtopts8=, /* add new options or replace existing */ stmtopts9=, /* options. Alternatively, the second */ stmtopts10=, /* value can be BEFORE or AFTER to add */ /* a new statement before or after the */ /* named statement. When the value is */ /* DELETE, the corresponding statement */ /* is deleted. The third is a */ /* statement name. All remaining */ /* options are options for that */ /* statement. In the example, an */ /* option is added to the second */ /* densityplot statement. The */ /* statement name is in most cases the */ /* first name, the one that begins the */ /* statement. The exception is */ /* layouts. In the case of layouts, */ /* use the second name (OVERLAY, */ /* GRIDDED, LATTICE, and so on). If */ /* an option is specified multiple */ /* times on a statement, the last */ /* specification overrides previous */ /* specifications. Hence, you do not */ /* need to know and respecify all of */ /* the options. You can just add an */ /* option to the end, and it will only */ /* affect one option. You can only */ /* use these options to modify */ /* statements that have a slash, and */ /* only modify the options that come */ /* after the slash. */ /* */ options=, /* specifies binary options, case is */ /* ignored. */ /* */ /* source - have PROC TEMPLATE display */ /* the generated source code. By */ /* default, it is not displayed. */ /* */ /* first - by default, the statement */ /* is added after the last title or */ /* footnote. Specify OPTIONS=FIRST to */ /* add the statement as the first */ /* title or footnote. Most graph */ /* templates provided by SAS do not */ /* use footnotes, so typically, this */ /* option only affects entry titles. */ /* */ /* By default, system titles and */ /* footnotes (those specified on title */ /* and footnote statements) do not */ /* appear in the graphs. You can use */ /* these options to change that: */ /* */ /* titles (along with the first */ /* option) - insert the system titles */ /* and footnotes before the entry */ /* titles and entry footnotes in the */ /* templates. */ /* */ /* titles (without the first option) - */ /* insert the system titles and */ /* footnotes after the entry titles */ /* and entry footnotes in the */ /* templates. */ /* */ /* replace - replace the entry titles */ /* and entry footnotes in the */ /* templates with the system titles */ /* and footnotes. If replace is */ /* specified, then titles is ignored. */ /* */ /* noquotes - specifies that the */ /* values of the system titles and */ /* footnotes are not to be quoted */ /* before they are added to the */ /* entrytitle or entryfootnote */ /* statement. With this option, you */ /* can individually and explicitly */ /* specify any options that you please */ /* on the titles or footnotes. */ /* However, you must ensure that you */ /* quote the part that provides the */ /* actual title or footnote. Here is */ /* an ordinary footnote: */ /* */ /* footnote 'My Footer'; */ /* */ /* This creates: */ /* */ /* entryfootnote "My Footer"; */ /* */ /* With options=noquotes, you can */ /* specify: */ /* */ /* footnote 'halign=left "My Footer"'; */ /* */ /* This creates: */ /* */ /* entrytitle halign=left "My Footer"; */ /* */ /* You can specify the titles/replace */ /* options and insert BY lines or */ /* just do one. If you do both, and */ /* you do not like where the BY */ /* line is going relative to your */ /* titles and footnotes, just */ /* specify OPTIONS=NOQUOTES and a */ /* string of '_byline0' to place the */ /* BY line where ever you choose. */ /* Example: */ /* title1 '"My First Title"'; */ /* title2 '_byline0'; */ /* title3 '"My Last Title"'; */ /* */ /* Also, you can embed BY information */ /* into a title or a footnote, again */ /* with OPTIONS=NOQUOTES. Example: */ /* */ /* title */ /* '"Spline Fit By Sex, " _byline0'; */ /* */ /* log - print a message in the log */ /* when each BY group is finished. */ /* */ steps=tgd, /* specifies the macro steps to run. */ /* Case and white space are ignored. */ /* By default, the macro modifies the */ /* template(s) (when t is specified) */ /* produces the graphs (when g is */ /* specified, and deletes the modified */ /* template(s) when d is specified. */ /* You can instead have it perform */ /* subsets of these three tasks by */ /* omitting terms from the STEPS= */ /* option. */ /* */ /* You do not have to delete the */ /* templates before you run your */ /* procedure again in the normal way. */ /* The template modification inserts */ /* the BY line through a macro */ /* variable and an MVAR statement. */ /* When the macro variable _ByLine0 is */ /* undefined, the EntryTitle or */ /* EntryFootNote statement drops out */ /* as if it were not there at all. */ /* */ by=, /* specifies the list of BY variables. */ /* Also see BYLIST=. */ /* */ bylist=);; /* specifies the full syntax of the */ /* BY statement. You can specify a */ /* full BY statement syntax including */ /* DESCENDING or NOTSORTED. If only */ /* BY variables are needed, only */ /* specify BY=. If you need options */ /* as well, then specify the BY */ /* variables in BY= and the full */ /* syntax in BYLIST=. */ /*-------------------------------------*/ /* This macro specifies OPTIONS */ /* NONOTES throughout most of its */ /* execution. If you want to see all */ /* the notes, submit the statement */ /* */ /* %let mktopts = notes; */ /* */ /* before running the macro. You can */ /* also specify VERSION to see the */ /* macro version. Example: */ /* */ /* %let mktopts = notes version; */ /*-------------------------------------*/ %local abort b dodelete dograph dotemp firstdot firstopt holdlast doit i istitle k lastby localver ngroups noby noquotes saveopts setnote tfafter tfbefore tfreplace time timeopt dumplog; %global _byline0 _mktver; %let timeopt = %scan(STIMER STATS, 1 + (&sysscp = OS)); %let saveopts = %sysfunc(getoption(notes)) %sysfunc(getoption(source)) %sysfunc(getoption(serror)); %let time = %sysfunc(datetime()); %let holdlast = &syslast; %let localver = 05May2009; %let _mktver = &localver; %let setnote = nonotes nosource; options noserror; %if %nrquote(&&mktopts) ne %nrstr(&)mktopts %then %do; %let k = %upcase(&mktopts); %if %index(&k, CHECKVERSION) %then %do; options serror; %goto mendit; %end; %if %index(&k, NOTES) %then %let setnote = notes; %if %index(&k, VERSION) %then %put ModTmplt macro version &localver.; %end; options serror; options &setnote; data _null_; length w $ 200 opt $ 500; call symputx('abort', '0', 'L'); call symputx('ngroups', '0', 'L'); call symputx('noby', symget('by') eq ' ', 'L'); abort = 0; %do i = 1 %to 10; opt = symget("stmtopts&i"); call symputx("dostmt&i", scan(opt, 1, ' '), 'L'); call symputx("stmtname&i", ' ', 'L'); w = lowcase(scan(opt, 2, ' ')); if not (w in ('add', 'replace', 'delete', 'before', 'after', ' ')) then do; put "ERROR: STMTOPTS&i=" opt " is invalid."; put 'ERROR: The second value must be ' 'ADD, REPLACE, DELETE, BEFORE or AFTER.'; abort = 1; end; if w ne ' ' then do; call symputx("stmttype&i", trim(w), 'L'); w = lowcase(scan(opt, 3, ' ')); layout = 0; if w = 'layout' then do; layout = 1; w = lowcase(scan(opt, 3 + layout, ' ')); end; if w = ' ' then do; put "ERROR: STMTOPTS&i=" opt " is invalid."; put "ERROR: A statement name must be specified as the third value."; abort = 1; end; call symputx("stmtname&i", trim(w), 'L'); call scan(opt, 4 + layout, i, l, ' '); if i then call symputx("stmtopts&i", substr(opt, i), 'L'); else call symputx("stmtopts&i", ' ', 'L'); end; %end; w = lowcase(symget('options')); i = index(w, 'first'); if i then substr(w, i , 6) = ' '; j = index(w, 'titles'); if j then substr(w, j + 5, 1) = ' '; j = index(w, 'title'); if j then substr(w, j , 5) = ' '; k = index(w, 'replace'); if k then substr(w, k , 7) = ' '; l = index(w, 'noquotes'); if l then substr(w, l + 7, 1) = ' '; l = index(w, 'noquote'); if l then substr(w, l , 7) = ' '; m = index(w, 'source'); if m then substr(w, m , 6) = ' '; o = index(w, 'log'); if o then substr(w, o , 3) = ' '; if k then j = 0; call symputx('firstopt', i gt 0 , 'L'); call symputx('tfbefore', j and i , 'L'); call symputx('tfafter', j and not i, 'L'); call symputx('tfreplace', k gt 0 , 'L'); call symputx('noquotes', l gt 0 , 'L'); call symputx('dumplog', o gt 0 , 'L'); if m then call symputx('setnote', symget('setnote') || ' source', 'L'); if w ne ' ' then do; put 'ERROR: The ' w "of 'OPTIONS=&options' is invalid."; abort = 1; end; if lowcase(symget('data')) = '_last_' or symget('data') = ' ' then call symputx('data', "&syslast", 'L'); w = symget('steps'); call symputx('dotemp' , index(w, 't') ne 0, 'L'); call symputx('dograph' , index(w, 'g') ne 0, 'L'); call symputx('dodelete', index(w, 'd') ne 0, 'L'); w = compress(w, 'tgd'); if w ne ' ' then do; put "ERROR: The STEPS=&steps option is invalid."; abort = 1; end; if symget('dograph') = '1' and symget('by') = ' ' then do; put 'ERROR: The BY= option must be specified.'; abort = 1; end; if (symget('dotemp') = '1' or symget('dodelete') = '1') and symget('template') = ' ' then do; put 'ERROR: The TEMPLATE= option must be specified.'; abort = 1; end; w = symget('file'); if symget('dotemp') = '1' and w = ' ' then do; put 'ERROR: The FILE= option must be specified.'; abort = 1; end; if symget('bylist') = ' ' then call symputx('bylist', symget('by'), 'L'); w = left(lowcase(symget('statement'))); if not (w =: 'entrytitle' or w =: 'entryfootnote') then do; put 'WARNING: The STATEMENT= option should specify an EntryTitle ' / ' or an EntryFootNote statement.' / ' Unexpected errors might occur.'; end; call symputx('istitle', w =: 'entrytitle', 'L'); if abort or _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %do; %let dodelete = 0; %goto endit; %end; options &setnote; /* %do i = 1 %to 10; %if &&dostmt&i ne %then %do; %put dostmt&i=&&dostmt&i; %put stmttype&i=&&stmttype&i; %put stmtname&i=&&stmtname&i; %put stmtopts&i=&&stmtopts&i; %end; %end; */ %if &tfreplace or &tfbefore or &tfafter %then %do; %if %nrbquote(&titles) = %then %do; proc sql; create view __byview as select * from dictionary.titles; quit; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %let titles = __byview; %end; data __bytitles(keep=type text); retain warn 0; set &titles; t = lowcase(text); if &noquotes and index(t,'_byline0') then call symputx('noby', '1', 'L'); if not warn and (index(t, '#byline') or index(t, '#byvar') or index(t, '#byval')) then do; warn = 1; put 'WARNING: ODS Graphics does not support #BY substitution.'; end; if _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %end; %else %do; data __bytitles; text = ' '; type = ' '; if _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %end; %if &dotemp %then %do; /* template modification */ * store template(s) in an external file; proc template; source &template / file=&file; run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; * add title/footnote and mvar statement to template; data _null_; length line temp $ 32767 w $ 40; retain temp ' '; infile &file lrecl=260 pad; input line $ 1-256; if _n_ = 1 then call execute('proc template;'); * Get full line for all but the most outrageous statements.; if substr(line, length(line), 1) ne ';' then do; temp = trim(temp) || ' ' || line; return; end; line = trim(temp) || ' ' || left(line); temp = ' '; dotitle = 0; w = lowcase(scan(line, 1, ' ;')); if w = 'define' and lowcase(scan(line, 2, ' ')) = 'statgraph' then done = &noby; if w = 'begingraph' and symget('by') ne ' ' then call execute("mvar _byline0;"); %if &tfreplace or &tfbefore or &tfafter %then %do; if &tfreplace and (w = 'entrytitle' or w = 'entryfootnote') then return; if ((&tfreplace or &tfbefore) and w = 'begingraph') or (&tfafter and w = 'endgraph') then dotitle = 1; %end; if not done and &firstopt and w = 'begingraph' then do; done + 1; link doline; if dotitle then link dotitle; call execute(symget('statement') || " _byline0;"); end; else if not done and not &firstopt and (( &istitle and w = 'layout' ) or (not &istitle and w = 'endgraph')) then do; done + 1; call execute(symget('statement') || " _byline0;"); if dotitle then link dotitle; link doline; end; else do; if dotitle and w = 'endgraph' then link dotitle; link doline; if dotitle and w = 'begingraph' then link dotitle; end; if _error_ then call symputx('abort', '1', 'L'); return; doline: if w = 'layout' then w = lowcase(scan(line, 2, ' ')); %if &dostmt1 ne %then %do; if w = "&stmtname1" then &stmtname1 + 1; %end; %do i = 2 %to 10; %let doit = 0; %if &&dostmt&i ne %then %do; %let doit = 1; %do j = 1 %to &i - 1; %if &&dostmt&j ne %then %do; %if &&stmtname&i = &&stmtname&j %then %let doit = 0; %end; %end; %end; %if &doit %then %do; if w = "&&stmtname&i" then &&stmtname&i + 1; %end; %end; %do i = 1 %to 10; %if &&dostmt&i ne %then %do; if w = "&&stmtname&i" and ("&&stmttype&i" = 'replace' or "&&stmttype&i" = 'add') then do; if &&dostmt&i = . or &&dostmt&i = &&stmtname&i then do; l = length(line); if substr(line, l, 1) = ';' then do; if "&&stmttype&i" = 'replace' and "&&stmtname&i" =: 'entry' then do; line = "&&stmtname&i" || ' ' || symget("stmtopts&i") || ';'; end; else do; i = 0; gone = 0; if "&&stmttype&i" = 'replace' then do; i = index(line, '/'); gone = 1; end; else gone = (index(line, '/') = 0); if i = 0 then i = l; substr(line, i) = ' '; temp = symget("stmtopts&i"); if temp eq ' ' then line = trim(line) || ';'; else do; if gone then line = trim(line) || ' /'; line = trim(line) || ' ' || trim(temp) || ';'; end; temp = ' '; end; end; else do; temp = symget("stmtopts&i"); put 'WARNING: Statement too long to modify.' / ' You must modify the template manually.' / temp / line; temp = ' '; end; end; end; %end; %end; %do i = 1 %to 10; %if &&dostmt&i ne %then %do; if w = "&&stmtname&i" and "&&stmttype&i" = 'before' then do; if &&dostmt&i = . or &&dostmt&i = &&stmtname&i then do; call execute(' ' || trim(symget("stmtopts&i")) || ';'); end; end; if w = "&&stmtname&i" and "&&stmttype&i" = 'delete' then do; if &&dostmt&i = . or &&dostmt&i = &&stmtname&i then do; line = ' '; end; end; %end; %end; call execute(trim(line)); %do i = 1 %to 10; %if &&dostmt&i ne %then %do; if w = "&&stmtname&i" and "&&stmttype&i" = 'after' then do; if &&dostmt&i = . or &&dostmt&i = &&stmtname&i then do; call execute(' ' || trim(symget("stmtopts&i")) || ';'); end; end; %end; %end; return; dotitle: dotitle = 0; * Insert system titles; do k = 1 to nobs; set __bytitles point=k nobs=nobs; type = upcase(type); if &noquotes then do; if type = 'T' then call execute("entrytitle &titleopts " || trim(text) || ';'); if type = 'F' then call execute("entryfootnote &titleopts " || trim(text) || ';'); end; else do; if type = 'T' then call execute("entrytitle &titleopts " || quote(trim(text)) || ';'); if type = 'F' then call execute("entryfootnote &titleopts " || quote(trim(text)) || ';'); end; end; return; run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %end; %if &dograph %then %do; /* graph creation */ * reorder the variables (kludgy, I know). Need vars in the order in which they appear in the list.; proc summary data=&data; class &by; output out=__by(drop=_type_ _freq_); ways 0; run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; * need names of BY variables in order; proc contents data=__by noprint out=__by; run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; proc sort; by varnum; run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; data _null_; length format name $ 100; set __by end=eof; * create numeric and character formats; if format = ' ' and formatl = 0 and formatd = 0 and type = 1 then do; format = 'best'; formatl = 12; end; if format = ' ' and type = 2 then do; format = '$'; formatl = length; end; if formatl then do; format = compress(format || put(formatl, 6.) || '.'); if formatd then format = compress(format || put(formatd, 6.)); end; if not index(format, '.') then format = trim(format) || '.'; * output each by variable name and format to a macro variable; call symputx('_byvar' || put(_n_, 5. -L), nliteral(name), 'L'); call symputx('_format' || put(_n_, 5. -L), format, 'L'); * output number of by variables and FIRST. for last BY variable; if eof then do; call symputx('_nbyvars', _n_, 'L'); if name = nliteral(name) then name = 'first.' || name; else do; name = nliteral(name); name = substr(name, 1, 1) || 'first.' || substr(name, 2); end; call symputx('firstdot', name, 'L'); end; if _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; * make copy of data set; data __bydata(drop=__where __name); length __where $ 32767 __name $ 256; retain __by " 0"; set &data end=eof; by &bylist; * construct by line for display in graph; if &firstdot then do; __where = ' '; %do i = 1 %to &_nbyvars; __k = &i; set __by(keep=name label rename=(name=__name label=__l)) point=__k; if __l ne ' ' then __name = __l; else __name = nliteral(__name); __where = trim(__where) || ' ' || trim(__name) || '=' || %if %nrbquote(%substr(&&_format&i,1,1)) = %nrbquote($) %then left(putc(&&_byvar&i, "&&_format&i")); %else left( put(&&_byvar&i, &&_format&i));; %end; __by = put(input(__by, 8.) + 1, 8. -L); call symputx('byline' || put(__by, 5. -L), left(__where), 'L'); end; if eof then call symputx('ngroups', __by, 'L'); if _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %if &ngroups = 0 %then %do; %put ERROR: Zero by groups.; %if %nrbquote(&bylist) ne %then %put WARNING: Probable error in BYLIST= or BY=.; %else %put WARNING: Probable error in BY=.; %end; * create plot for each BY group; %do b = 1 %to &ngroups; data _null_; call symputx('_byline0', symget("byline&b"), 'G'); if _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %mygraph where __by = "&b"; by &bylist; run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %if &dumplog %then %do; data _null_; length line $ 32767; line = symget('_byline0'); put 'Note: Processed group ' line +(-1) '.'; if _error_ then call symputx('abort', '1', 'L'); run; %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit; %end; %end; %end; %endit: %if &dodelete %then %do; /* clean up */ proc template; delete &template; run; %if &syserr > 4 %then %let abort = 1; %end; proc datasets nolist; delete __by __bydata __byview(memtype=view) __bytitles; run; quit; %if &syserr > 4 %then %let abort = 1; %let syslast = &holdlast; options &saveopts; %if &abort %then %do; %if &syserr = 1016 or &syserr = 116 %then %put ERROR: Insufficient memory.; %if &syserr = 2000 or &syserr = 3000 %then %put ERROR: Syntax error. Check your macro arguments for validity.; %put ERROR: The ModTmplt macro ended abnormally.; %end; %if %sysfunc(getoption(&timeopt)) = &timeopt and %sysfunc(getoption(stimer)) = STIMER %then %do; %let time = %sysfunc(round(%sysevalf(%sysfunc(datetime()) - &time), 0.01)); %put NOTE: The ModTmplt macro used &time seconds.; %end; %mend;