BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
P5C768
Obsidian | Level 7

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.

1 ACCEPTED SOLUTION

Accepted Solutions
GraphGuy
Meteorite | Level 14

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 ...

http://support.sas.com/documentation/cdl/en/graphref/63022/HTML/default/viewer.htm#annotate_position...

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;

View solution in original post

12 REPLIES 12
GraphGuy
Meteorite | Level 14

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 ...

http://support.sas.com/documentation/cdl/en/graphref/63022/HTML/default/viewer.htm#annotate_position...

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;

P5C768
Obsidian | Level 7

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;

GraphGuy
Meteorite | Level 14

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).

P5C768
Obsidian | Level 7

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;

P5C768
Obsidian | Level 7

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.

MikeZdeb
Rhodochrosite | Level 12

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;


usmap.png
P5C768
Obsidian | Level 7

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.

GraphGuy
Meteorite | Level 14

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:

http://robslink.com/SAS/democd31/church.htm

http://robslink.com/SAS/democd31/church_info.htm

MikeZdeb
Rhodochrosite | Level 12

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;

P5C768
Obsidian | Level 7

But can you use the %annomac to annotate multiple items per county?

MikeZdeb
Rhodochrosite | Level 12

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

MikeZdeb
Rhodochrosite | Level 12

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;


map_with_text.png

sas-innovate-2024.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

Register now!

How to Concatenate Values

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.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 12 replies
  • 7034 views
  • 3 likes
  • 3 in conversation