Hello Community,
I'm producing a series of graphs with long titles and axis labels. Using SGPLOT (SAS version: 9.04.01M5P091317) and resulted images are very inconsistent, some of them has printed the whole title/label wrapping it in multiple lines when necessary, but others just truncate the same labels. I'm outputting images into .rtf file. So far tried to adjust image/font size, rtf page size etc. but couldn't find a solution.
Looking at the sas dosumentation on GTL I've tried to modify the image template, but the options provided in the documentation don't let me find the right template source . Created log file is missing the template name and the source (see the code and log below.)
Besides, even if I find the source template I've no idea about the options that need to be applied to allocate more space for labels and titles.
/*Modify graph template*/
ods trace on;
ods graphics on;
proc sgplot data=have description='' noborder noautolegend;
scatter x=height y=age;
reg x=height y=age;
run;
ods trace off;
98 ods trace on; 99 ods graphics on; 100 proc sgplot data=have description='' noborder noautolegend; 101 scatter x=height y=age; 102 reg x=height y=age; 103 run; NOTE: PROCEDURE SGPLOT used (Total process time): real time 0.13 seconds cpu time 0.06 seconds Output Added: ------------- Name: SGPlot Label: ' ' Path: SGPlot.SGPlot ------------- NOTE: There were 19 observations read from the data set WORK.HAVE. 104 ods trace off;
Below is the simulated image and output with long labels using sashelp.class data. As seen in the picture X-axis labels get truncated while Y-axis wrapped up.
Could anyone provide some guidance, please, on how to resolve the truncation issue: either using SGPLOT options or by modifying image template using GTL.
data have;
set sashelp.class;
label age= 'This variable has a very long label that needs to be wrapped up in the y-axis of the graph'
height= 'This variable has a very long label that needs to be wrapped up in the x-axis of the graph';
run;
options nodate nonumber;
ods rtf file='test.rtf' contents=yes toc_data;
ods noproctitle;
ods proclabel='Correlation of Age and Height in Sashelp.class dataset';
title font='Times New Roman' height=10pt j=c 'Correlation of Age and Height in Sashelp.class dataset';
ods graphics/imagefmt=png width=650px height=650px;
proc sgplot data=have description='' noborder;
scatter x=height y=age;
reg x=height y=age;
run;
ods rtf close;
Unfortunately, there is currently a limitation in ODS Graphics where only one axis label can be wrapped at a time. The SGPLOT procedure uses heuristics to determine which axis should be allowed to wrap. There are a couple of alternatives for your situation:
1. Turn off the bottom axis label and annotate it.
2. In GTL, turn off the bottom axis label and use ENTRYs in a gridded layout -- something like the following:
proc template;
define statgraph label;
begingraph;
layout gridded;
layout overlay / yaxisopts=(label='This variable has a very long label that needs to be wrapped up in the y-axis of the graph' labelfitpolicy=split) xaxisopts=(display=(line ticks tickvalues));
scatterplot x=age y=height;
endlayout;
entry " This variable has a very long label that needs";
entry " to be wrapped up in the y-axis of the graph";
endlayout;
endgraph;
end;
run;
proc sgrender data=sashelp.class template=label;
run;
I would probably favor option #1
Hope this helps!
Unfortunately, there is currently a limitation in ODS Graphics where only one axis label can be wrapped at a time. The SGPLOT procedure uses heuristics to determine which axis should be allowed to wrap. There are a couple of alternatives for your situation:
1. Turn off the bottom axis label and annotate it.
2. In GTL, turn off the bottom axis label and use ENTRYs in a gridded layout -- something like the following:
proc template;
define statgraph label;
begingraph;
layout gridded;
layout overlay / yaxisopts=(label='This variable has a very long label that needs to be wrapped up in the y-axis of the graph' labelfitpolicy=split) xaxisopts=(display=(line ticks tickvalues));
scatterplot x=age y=height;
endlayout;
entry " This variable has a very long label that needs";
entry " to be wrapped up in the y-axis of the graph";
endlayout;
endgraph;
end;
run;
proc sgrender data=sashelp.class template=label;
run;
I would probably favor option #1
Hope this helps!
@DanH_sas ,
Thank you so much for guidance, it was super helpful!
Good to know about ODS graphics axis label behaviors. While I'm still willing to work on your option 1 (still need to learn how to annotate and apply it), I just started from option 2 using hints from @ballardw . I'm getting closer to desired result, but still need a little adjustments. Above I've described those adjustments, so if you have any suggestions, please let me know.
You don't have to create a separate data set to create labels. Just use the data set and the LABEL statement in the Procedure.
You can create the template code without using ods trace. Use the Proc Sgplot option TMPLOUT="filename" on the proc statement. The option creates a text file (i.e. if named with .sas extension a program code file) at the location and name you provide. My example uses windows writing to the root of the D drive. You need to provide valid path and file name for your system.
proc sgplot data=sashelp.class description='' noborder noautolegend tmplout="d:\yourfile.txt" ; scatter x=height y=age; reg x=height y=age; label age= 'This variable has a very long label that needs to be wrapped up in the y-axis of the graph' height= 'This variable has a very long label that needs to be wrapped up in the x-axis of the graph'; run;
I didn't add all of your options, the code above shows this for the generated template:
proc template; define statgraph sgplot; begingraph / collation=binary subpixel=on; layout overlay / walldisplay=(fill) yaxisopts=(labelFitPolicy=Split) y2axisopts=(labelFitPolicy=Split); ScatterPlot X=Height Y=Age / subpixel=off primary=true LegendLabel="This variable has a very long label that needs to be wrapped up in the y-axis of the graph" NAME="SCATTER"; ScatterPlot X=Height Y=Age / primary=true; RegressionPlot X=Height Y=Age / NAME="REG" LegendLabel="Regression" Maxpoints=2; endlayout; endgraph; end; run;
Your ODS RTF output uses a different style template than the results window by default. So you should test and specify a specific ODS style for a better chance at consistency. I think that style STYLES.RTF defaults to larger fonts in some places so you might try a style which uses smaller fonts and/or XAXIS and YAXIS statements Labelattrs, control label text appearance, Splitchar, to control where label text splits, and maybe Splitjustify for alignment of secondary lines.
Proportional fonts mean that text with the same number of characters takes up different amounts of space in the display and "simple" looking at length of a string is difficult. So you may have to take some time with your actual long strings to have good control.
An example of the difference in space as this window typically uses a proportional font:
iiiiiiiiii
WWWWWWWWWW
There are 10 characters in each of those strings but the second takes almost 4 times the display space. Using characteristics like bold and italic can drastically alter the display length as well.
So be prepared to spend a lot of time and possible heartbreak over things not quite fitting.
Thank you, @ballardw for the detailed information. TMPLOUT="filename" option was very useful. I could easily see my template options and modify them as needed.
So far, modified template allows all titles and axis labels to fit.
I'm using ENTRY statements as @DanH_sas suggested for x-axis labels which challenges me to figure out the splitting point of long labels. Using %SUBSTR function I could split it, but it is also truncating whole words into pieces, unfortunately I couldn't figure out to split the string into whole word parts. Alternatively, I might add explicitly "separators" inside the labels and use %scan function or other options (splitchar maybe). But I'm not sure if this would be the (best) solution. Besides, I need to eliminate the "SGRENDER PROCEDURE" name from the TOC. In majority of SG procedures CONTENTS='' option drops proc names from TOC. It seems this option doesn't work for SGRENDER.
Below I'm providing my actual code for readers review, and wonder anyone could suggest how to fix those 2 minor issues.
options nodate nonumber ;
ods escapechar='~';
ODS rtf FILE = "&mypath\InterItem corr.rtf" contents=yes toc_data ;
%macro items1(xitems=, yitems=)/minoperator;
%do x=1 %to 8;
%do y=1 %to 8;
%let xitem= %scan(&xitems, &x);
%let yitem= %scan(&yitems, &y);
%if &xitem ne &yitem %then %do;
ods proclabel="Figure 1.&x..&y: Inter-Item correlation of &&&xitem and &&&yitem at Baseline";
proc template;
define statgraph label;
begingraph;
entrytitle halign=center "Figure 1.&x..&y: Inter-Item correlation of &&&xitem and &&&yitem at Baseline"/ textattrs=( family="Arial" size=9pt weight=bold) halignCenter=Graph;
layout gridded;
layout overlay / xaxisopts=( type=linear linearopts=( tickvaluelist=( 1 2 3 4 5 ) viewmin=1 viewmax=5 Integer=true ) display=(line ticks tickvalues) )
y2axisopts=(labelFitPolicy=Split labelattrs=( family="Arial" size=9pt weight=normal))
yaxisopts=( labelposition=datacenter Label="&&&yitem" labelattrs=( family="Arial" size=9pt weight=normal) labelFitPolicy=Split type=linear linearopts=( tickvaluelist=( 1 2 3 4 5 ) viewmin=1 viewmax=5 Integer=true ) )
y2axisopts=(labelFitPolicy=Split labelattrs=( family="Arial" size=9pt weight=normal));
ScatterPlot X=&xitem Y=&yitem / subpixel=off jitter=auto primary=true NAME="SCATTER";
ScatterPlot X=&xitem Y=&yitem / primary=true;
RegressionPlot X=&xitem Y=&yitem / Lineattrs=( Color=CX000000 Thickness=2) Maxpoints=2;
endlayout;
entry " %qsubstr(&&&xitem, 1, 70)";
entry " %qsubstr(&&&xitem, 71)";
endlayout;
endgraph;
end;
run;
ods noproctitle;
proc sgrender data=items template=label;
run;
title;footnote;
%end;
%end;
%end;
%mend;
%items1(xitems=PFA0101 PFA0102 PFA0103 PFA0104 PFA0105 PFA0106 PFA0107 PFA0108, yitems= PFA0101 PFA0102 PFA0103 PFA0104 PFA0105 PFA0106 PFA0107 PFA0108);
ODS _all_ CLOSE;
ods html;
@A_Kh wrote:
Thank you, @ballardw for the detailed information. TMPLOUT="filename" option was very useful. I could easily see my template options and modify them as needed.
Alternatively, I might add explicitly "separators" inside the labels and use %scan function or other options (splitchar maybe). But I'm not sure if this would be the (best) solution.
Try adding a variable label to the basic example from before but use the SPLITCHAR= option with a character like * (asterisk was a default splitchar when I started using SAS in 1980's) and see how that changes the template. That may give you ideas.
Note that the split isn't only for controlling wrapping, sometimes it just makes sense to have a visual break and controlling where with splitchar is one of the easier ways.
Try the SGRENDER option OBJECTLABEL="text string" and see if that changes the appearance of the Contents.
OBJECTLABEL="" option did work.
However couldn't get desired results with SPLITCHAR, which I assume due to misuse of the option. Instead, I went the easiest way for me, that is inserting a special character "#" into item labels and use %scan function for line breaks. It did give me the output I need.
I leave the discussion open for a day or so to see if any new suggestions come up. In general, the question has been resolved with Community memebers help.
Below is my final code.
%let PFA0101 = PROMIS SF 8c - 'Are you able to bend down and #pick up clothing from the floor?' item;
%let PFA0102 = PROMIS SF 8c - 'Are you able to stand up from an armless straight chair?' item#;
%let PFA0103 = PROMIS SF 8c - 'Are you able to dress yourself, #including tying shoelaces and buttoning your clothes?' item;
%let PFA0104 = PROMIS SF 8c - 'Are you able to go up and down stairs at a normal pace?' item#;
%let PFA0105 = PROMIS SF 8c - 'Are you able to wash and dry your body?' item#;
%let PFA0106 = PROMIS SF 8c - 'Are you able to go for a walk of at least 15 minutes?' item#;
%let PFA0107 = PROMIS SF 8c - 'Does your health now limit you in doing vigorous activities, #such as running, lifting heavy objects, participating in strenuous sports?' item;
%let PFA0108 = PROMIS SF 8c - 'How much difficulty do you have doing your daily physical activities #because of your health?' item;
options nodate nonumber ;
ODS rtf FILE = "&mypath\InterItem corr_Test.rtf" contents=yes toc_data ;
%macro items1(xitems=, yitems=)/minoperator;
%do x=1 %to 8;
%do y=1 %to 8;
%let xitem= %scan(&xitems, &x);
%let yitem= %scan(&yitems, &y);
%if &xitem ne &yitem %then %do;
ods proclabel="Figure 1.&x..&y: Inter-Item correlation of %sysfunc(tranwrd(&&&xitem, #, %str())) and %sysfunc(tranwrd(&&&yitem, #, %str())) at Baseline";
proc template;
define statgraph label;
begingraph;
entrytitle halign=center "Figure 1.&x..&y: Inter-Item correlation of %sysfunc(tranwrd(&&&xitem, #, %str())) and %sysfunc(tranwrd(&&&yitem, #, %str())) at Baseline"/ textattrs=( family="Arial" size=9pt weight=bold) halignCenter=Graph;
layout gridded;
layout overlay / xaxisopts=( type=linear linearopts=( tickvaluelist=( 1 2 3 4 5 ) viewmin=1 viewmax=5 Integer=true ) display=(line ticks tickvalues) )
y2axisopts=(labelfitpolicy=split labelattrs=( family="arial" size=9pt weight=normal))
yaxisopts=( labelposition=datacenter label="%sysfunc(tranwrd(&&&yitem, #, %str()))" labelattrs=( family="arial" size=9pt weight=normal) labelfitpolicy=split type=linear linearopts=( tickvaluelist=( 1 2 3 4 5 ) viewmin=1 viewmax=5 integer=true ) )
y2axisopts=(labelfitpolicy=split labelattrs=( family="arial" size=9pt weight=normal));
scatterplot x=&xitem y=&yitem / subpixel=off jitter=auto primary=true name="scatter";
scatterplot x=&xitem y=&yitem / primary=true;
regressionplot x=&xitem y=&yitem / lineattrs=( color=cx000000 thickness=2) maxpoints=2;
endlayout;
entry " %scan(&&&xitem, 1, %str(#))";
entry " %scan(&&&xitem, 2, %str(#))";
endlayout;
endgraph;
end;
run;
ods noptitle;
proc sgrender data=items OBJECTLABEL="" template=label;
run;
title;footnote;
%end;
%end;
%end;
%mend;
%items1(xitems=PFA0101 PFA0102 PFA0103 PFA0104 PFA0105 PFA0106 PFA0107 PFA0108, yitems= PFA0101 PFA0102 PFA0103 PFA0104 PFA0105 PFA0106 PFA0107 PFA0108);
ods _all_ close;
ods html;
The little trick is using "0A"x character .
But intesting thing is character connecting operator “||” is not working here. I don't know why? I am using SAS OnDemand for Academic . Here is an example:
data have; set sashelp.class; label age= "This variable has a very long label that" '0A'x "needs to be wrapped up in the y-axis of the graph" height= 'This variable has a very long label that' '0A'x 'needs to be wrapped up in the x-axis of the graph'; run; title font='Times New Roman' height=10pt j=c 'Correlation of Age and Height in Sashelp.class dataset'; ods graphics/imagefmt=png width=650px height=650px; proc sgplot data=have description='' noborder; scatter x=height y=age; reg x=height y=age; run;
data have; set sashelp.class; label age= "This variable has a very long label that"||'0A'x||"needs to be wrapped up in the y-axis of the graph" height= 'This variable has a very long label that'||'0A'x||'needs to be wrapped up in the x-axis of the graph'; run; title font='Times New Roman' height=10pt j=c 'Correlation of Age and Height in Sashelp.class dataset'; ods graphics/imagefmt=png width=650px height=650px; proc sgplot data=have description='' noborder; scatter x=height y=age; reg x=height y=age; run;
Thank you, @Ksharp for the suggestion. I'll try this trick on my SAS windowing environment.
@Ksharp wrote:
The little trick is using "0A"x character .
But intesting thing is character connecting operator “||” is not working here. I don't know why? I am using SAS OnDemand for Academic . Here is an example:
data have; set sashelp.class; label age= "This variable has a very long label that" '0A'x "needs to be wrapped up in the y-axis of the graph" height= 'This variable has a very long label that' '0A'x 'needs to be wrapped up in the x-axis of the graph'; run; title font='Times New Roman' height=10pt j=c 'Correlation of Age and Height in Sashelp.class dataset'; ods graphics/imagefmt=png width=650px height=650px; proc sgplot data=have description='' noborder; scatter x=height y=age; reg x=height y=age; run;
The concatenation operator only works in character expressions. The "implied" concatenation that you are using is not uncommon, title, footnote, statements and in proc format.
You don't get an error because it is not an error.
@Ksharp wrote:
John.King,
Thanks for clarify this.
But I think SAS LOG should pop up a WARNING/ERROR message to let me know this syntax of code was NOT right.
But it give me nothing, therefore I thought this syntax of code was right. Right?
@Ksharp wrote:
John.King,
But concatenation operator || would appear in graph. Wouldn't it get you a surprise ?
Because at that point || is not an operator, it just another unquoted set of characters to add to the label. Just like if I used other "operators" as part of the label.
data have;
set sashelp.class (obs=1);
label age= 'Word one' two 'three four' five
sex= + | & and or ||
;
run;
proc print label;
run;
It's finally time to hack! Remember to visit the SAS Hacker's Hub regularly for news and updates.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.