Is it possible to add multiple annotations to a map? For example, I would like to include a percentage by state on a basic map of the US, but can I also add the numerator and denominator that make up the percentage? I have also noticed that although I format my percentage in my data set as percent9.2, it shows up as a decimal when the map is generated. Is it possible for it to show up as a percentage in the xx.x% format? Thanks.
The easiest way of adding multiple lines of text annotation is to use the 'positions' to place the text at various locations in relation to the x/y point ...
And in order for your format to show up in the annotated 'text' variable, you can use a 'put()' statement, and specify the format there.
I'm including a small sample that shows both of these things:
data anno_stuff; set maps.uscenter (where=(fipstate(state) in ('NC' 'SC' 'GA' 'FL')));
xsys='2'; ysys='2'; hsys='3'; when='a';
function='label';
length text $20;
position='2'; text=fipstate(state); output;
position='5'; text=fipnamel(state); output;
position='8'; text='Lat.='||trim(left(put(lat,comma5.2))); output;
run;
pattern1 v=solid color=white;
proc gmap data=maps.us map=maps.us (where=(fipstate(state) in ('NC' 'SC' 'GA' 'FL')));
id state;
choro state / levels=1 anno=anno_stuff;
run;
The easiest way of adding multiple lines of text annotation is to use the 'positions' to place the text at various locations in relation to the x/y point ...
And in order for your format to show up in the annotated 'text' variable, you can use a 'put()' statement, and specify the format there.
I'm including a small sample that shows both of these things:
data anno_stuff; set maps.uscenter (where=(fipstate(state) in ('NC' 'SC' 'GA' 'FL')));
xsys='2'; ysys='2'; hsys='3'; when='a';
function='label';
length text $20;
position='2'; text=fipstate(state); output;
position='5'; text=fipnamel(state); output;
position='8'; text='Lat.='||trim(left(put(lat,comma5.2))); output;
run;
pattern1 v=solid color=white;
proc gmap data=maps.us map=maps.us (where=(fipstate(state) in ('NC' 'SC' 'GA' 'FL')));
id state;
choro state / levels=1 anno=anno_stuff;
run;
Thank you very much for the response. When I try to translate the coding you provided to use my data, it appears that the values are present, but in a single line across the map, rather than by state. Do I need to take an extra step since I am not trying to map data from the maps.us table, but rather from my data, linked by statecode?
Here is the code I am using, in case that is needed.
data anno_stuff;
set Mods_by_State;
xsys='2'; ysys='2'; hsys='3'; size=3; when='a';
function='label';
length text $20;
position='2'; text=ModPercent; output;
position='5'; text=TotWOCount; output;
run;
pattern1 v=solid color=vligb;
proc gmap data=Mods_by_State map=maps.us;
id StateCode;
choro ModPercent / levels=6 anno=anno_stuff;
run;
Are you setting an X and Y value for the position of the text labels somewhere?
You could use the X and Y from maps.uscenter (for example, merge that with your data set that has the labels).
I was not including X & Y variables at first, but even though I am now including them, I still get the same result. The code I am using is below.
data counts;
set hspdata.counts_2011(where=(resolvedte ge '01Oct2011'd)) hspdata.counts_2012;
length StateCode $2.;
StateCode = substr(SRV_REGN,1,2);
run;
%let Beg_MTD = '01Apr12'd;
%let End_MTD = '30Apr12'd;
proc summary data = WO_Counts(where=(evt_stat_cd in ("Closed") and
&beg_mtd. le Resolvedte le &end_mtd.)) missing nway;
class StateCode;
var Change;
output out = Temp_by_State(rename=(_freq_=TotCnt) drop = _type_) sum=;
run;
proc sql;
create table tempUSmap as
select t1.statecode, t2.*
from maps.us as t1 left join maps.uscenter as t2 on t1.state = t2.state;
quit;
proc sort data= tempUSmap nodupkey;
by StateCode;
Run;
proc sql;
create table tempUSmap_withmods as
select t1.*, t2.*
from tempUSmap as t1 left join Temp_by_State as t2 on t1.statecode = t2.statecode;
quit;
data Mods_by_State;
Set tempUSmap_withmods;
length Group $8.;
APercent=round(Change/TotWOCnt,0.0001);
format APercent percent9.2;
if APercent <0.4 then Group = "<40%";
if APercent >=0.4 and APercent <0.5 then Group = "40%-49%";
if APercent >=0.5 and APercent <0.6 then Group = "50%-59%";
if APercent >=0.6 and APercent <0.7 then Group = "60%-69%";
if APercent >=0.7 then Group = ">70%";
xsys='2'; ysys='2'; hsys='3'; size=3; when='a';
function='label';
length text $20;
position='2'; text=put(APercent, percent9.1); output;
run;
goptions reset=all border;
pattern1 v=ms c=cxeff3ff;
pattern2 v=ms c=cxbdd7e7;
pattern3 v=ms c=cx6baed6;
pattern4 v=ms c=cx2171b5;
legend1
origin=(75,60)pct
across=1
mode=share
label=(position=top j=c 'Modification Rate')
shape=bar(3,4)pct
cborder=blue
;
title1 "Modifications by State";
footnote1 j=r &sysdate;
legend1 label=("Modifiation Percent");
pattern1 v=msolid color=vligb;
proc gmap
map=maps.us
data=Mods_by_State;
id StateCode;
choro Group / levels=6 annotate=anno_stuff;
run;
quit;
Actually, I figured it out. In addition to merging the maps.us and maps.uscenter so that I had the center values by StateCode, I had to change the data Mods_by_State statement into two data steps, one with the annotate example you provided and one with my data set. Thanks again to both of you.
Robert, one further question. i noticed on your website you have some examples where you have states that have data offset and connected by a leader line (i.e. the east coast data values are off to the right of the graph to avoid overlap). How is that done? I have been searching around for that code and have been unable to find it as of yet. Thanks.
hi ... the USCENTER data set has two observations for some states (extra X/Y coordinates for adding labels)
there's a variable named OCEAN and if that variable has a value of 'Y' the X/Y values are in the Atlantic Ocean while the X/Y values for the same state with OCEAN = 'N' are at the state center
* make a new data set ... place X/Y values for same state in one observation;
data new;
merge
maps.uscenter (where=(ocean eq 'N'))
maps.uscenter (where=(ocean eq 'Y') rename=(x=xo y=yo));
by state;
drop lat long;
run;
* if a state has only one set of X/Y values, draw a label;
* if a state has two sets of X/Y values, draw a line, then draw a label;
data anno;
retain xsys ysys '2' hsys '3' style '"calibri"' size 2 function 'label' cbox 'graydd' when 'a';
set new;
text = fipstate(state);
if ocean eq 'N' then output;
else do;
size = 0.25;
function = 'move';
output;
x = xo;
y = yo;
function = 'draw';
output;
size = 2;
function = 'label';
output;
end;
run;
goptions reset=all;
pattern1 v=msolid color=white;
proc gmap data=maps.us map=maps.us anno=anno;
id state;
choro state / levels=1 nolegend;
run;
quit;
I have a follow up question: is the same thing possible using US county, even though there is no US county center map? I would like to limit my results by state and county, and then display several pieces of information by county, one of which being COUNTYNM from the maps.cntyname table. Thanks.
We don't ship a data set with the pre-calculated center of each county, but we ship the %centroid macro that you can use to estimate the centers of the areas in any of your SAS maps. Here's an example of how to run the centroid macro:
/* Find the center of each county */
%annomac;
%centroid( mymap, centers, state county );
And here is an example that I used the above code in:
Hi ... a follow up to Robert's posting ... the %CENTROID macro requires a data set that's sorted according to the ID variable(s). So, finding the center of each county would require ...
proc sort data=maps.counties out=mymap;
by state county;
run;
But can you use the %annomac to annotate multiple items per county?
hi ... %annomac just allows you to use the SAS-supplied macros, like %centroid
once you know the centroids, it's easy (sort of) to add multiple items
take a look at Faking ArcGIS maps in SAS ... http://www.nesug.org/Proceedings/nesug11/gr/gr08.pdf
Hi ... Robert answered your multiple lines of text question. Here's his example (modified a bit) to show how to get the percentages to appear properly ...
* data set with numeric variables NUM, DEN, Z formatted PRECENT9.2;
data x;
set maps.uscenter (where=(fipstate(state) in ('NC' 'SC' 'GA' 'FL')));
num = ceil(10000*ranuni(999));
den = ceil(100000*ranuni(999));
z = num / den;
format z percent9.2;
run;
* convert numeric variables to text with PUT statements ... FORMAT controls appearance;
data anno_stuff;
retain xsys ysys '2' hsys '3' when 'a' function 'label' style '"calibri"' size 2;
length text $50;
set x;
position='2'; text=catt(fipnamel(state), ':' , put(z,percent9.1)); output;
position='5'; text=put(num,comma10.); output;
position='8'; text=put(den,comma10.); output;
run;
pattern1 v=msolid color=white;
proc gmap data=maps.us map=maps.us (where=(fipstate(state) in ('NC' 'SC' 'GA' 'FL')));
id state;
choro state / levels=1 nolegend anno=anno_stuff;
run;
quit;
Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.
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.