/*********************************************************************/ /* Step 1 ************************************************************/ /*********************************************************************/ /* Assign the desired state & zipcodes here ... Change the state abbreviation, and then enter one (or more) zipcodes you know to be in that state. Submit your sas code, and you should see the zipcodes show up on your map. */ %let state=PA; data my_zips; input zip; datalines; 19131 17013 15767 15295 16046 16364 ; run; /* Look up the longitude/latitude for the zipcodes */ proc sql; create table circle_anno as select my_zips.zip, zipcode.city, -1*zipcode.x as longitude, zipcode.y as latitude /* If you don't have the new v9 zipcity() function, could merge in city name here */ /* , zipcode.city */ from my_zips left join sashelp.zipcode on my_zips.zip=zipcode.zip; quit; run; /* Convert degrees to radians, and use new v9 zipcity() function to get city name */ data circle_anno; set circle_anno; length city $ 30; city=scan(zipcity(zip),1,','); x=atan(1)/45 * longitude; y=atan(1)/45 * latitude; run; /* Create annotate 'circle' and chart-tip info for the city Also, for fun, add a url link to get weather info */ data circle_anno; length function style color $ 8 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3' when 'a'; set circle_anno; /* Create html title= charttip and href= drilldown for each annotated marker */ html= 'title='||quote( trim(left(propcase(city)))||' ('||trim(left(zip))||')' ) ||' '|| 'href='||quote( 'http://www.mapquest.com/maps/map.adp?formtype=address&searchtype=address&country=US&zipcode='||trim(left(zip)) ); /* Annotate a solid-filled pie at each zipcode */ /* a pie centers better than the 'special' dot character */ function='pie'; color='graycc'; style='psolid'; position='5'; rotate=360; size=6; anno_flag=1; output; /* Annotate the city name */ function='label'; position='B'; style='"arial"'; text=trim(left(city)); /* color='green'; */ color='black'; cbox='white'; size=4; anno_flag=3; output; run; /* Use unprojected maps.county, rather than maps.uscounty, so you can annotate graphics at long/lat, and re-project it. */ data my_map; set maps.county (where=(fipstate(state)="&state")); run; /* Creates an annotate version of the county outlines, so that you can annotate them such that the county outlines appear "on top of" the area circles - this isn't essential, but it looks a lot better. This gives the circles a more 'transparent' look, and allows you to see the county outlines that would have otherwise been covered up by the circles. (This code is slightly modified version of some code provided by tech support.) */ /*********************************************************************/ /* Step 2 ************************************************************/ /*********************************************************************/ /* Uncomment the 'beef' of the following data step to see border 'transparent' effect, on the annotated dots. */ data border_anno; length COLOR FUNCTION $ 8; retain XSYS YSYS '2' COLOR 'white' SIZE 1.75 WHEN 'A' FX FY FUNCTION; anno_flag=2; /* set my_map; by County Segment; if first.Segment then do; FUNCTION = 'Move'; FX = X; FY = Y; end; else if FUNCTION ^= ' ' then do; if X = . then do; X = FX; Y = FY; output; FUNCTION = ' '; end; else FUNCTION = 'Draw'; end; if FUNCTION ^= ' ' then do; output; if last.Segment then do; X = FX; Y = FY; output; end; end; */ run; /* project the map, and the annotation (this 'straightens' the selected state map) */ data combined; set my_map circle_anno border_anno; run; proc gproject data=combined out=combined dupok; id state; run; data my_map circle_anno border_anno name_anno; set combined; if anno_flag=1 then output circle_anno; else if anno_flag=2 then output border_anno; else if anno_flag=3 then output name_anno; else output my_map; run; /* Layer the annotate so that they are drawn in desired order, and so that the proper pieces look "on top" */ data top_anno; set circle_anno border_anno name_anno; run; /* Create an annotated 'shadow' to go behind the map. It's ok to use the already-projected map for this. */ /*********************************************************************/ /* Step 3 ************************************************************/ /*********************************************************************/ /* Uncomment the 'beef' of the following data step to see border 'shadow' effect, around the edge of the map. */ data anno_shadow; /* set my_map; orig_order+1; run; proc sort data=anno_shadow out=anno_shadow; by county segment orig_order; run; proc gremove data=anno_shadow out=anno_shadow; by county; id county; run; data anno_shadow; length COLOR FUNCTION $ 8; retain FX FY FUNCTION; set anno_shadow; by county segment; xsys='2'; ysys='2'; color='black'; size=1.75; when='B'; style='msolid'; if first.segment then do; FUNCTION = 'poly'; FX = X; FY = Y; end; else if FUNCTION ^= ' ' then do; if X = . then do; X = FX; Y = FY; output; FUNCTION = ' '; end; else FUNCTION = 'polycont'; end; if FUNCTION ^= ' ' then do; output; if last.segment then do; X = FX; Y = FY; output; end; end; */ run; /* Give a little x & y offset, so it will look like a shadow */ data anno_shadow; set anno_shadow; x=x+.0004; y=y-.0006; run; /* Annotated gradient shaded background - this is just a bunch of rectangular annotated areas (wide, short, strips), each one a progressively lighter color. */ /*********************************************************************/ /* Step 4 ************************************************************/ /*********************************************************************/ /* Uncomment the 'beef' of the following data step to see the gradient background. */ data anno_gradient; /* length function style color $10; retain xsys '3' ysys '3' when 'b' style 'solid'; orig_red= input('05',hex2.); orig_green=input('70',hex2.); orig_blue= input('B0',hex2.); do i=100 to 0 by -2; count+1; function='move'; x=0; y=i; output; function='bar'; x=100; y=i-2; percent=(count-1)/(45); red=orig_red + ( (256-orig_red) * percent ); if red > 255 then red=255; green=orig_green + ( (256-orig_green) * percent ); if green > 255 then green=255; blue=orig_blue + ( (256-orig_blue) * percent ); if blue > 255 then blue=255; rgb=put(red,hex2.)||put(green,hex2.)||put(blue,hex2.); color="cx"||rgb; output; end; */ run; /* The gradient and the shadow go behind (when='b') the rest of the map. I find it easiest to keep them in a separate annotate data set. */ data anno_back; set anno_gradient anno_shadow; if function ne '' then output; run; goptions gunit=pct htitle=7 htext=5 ftitle="arial/bo" ftext="arial" ctitle=white ctext=white; title "Map of special zipcodes in &state"; title2 "Click on markers to see more info"; /* Add some space on left & right of the map */ title3 h=5pct a=90 " "; title4 h=5pct a=-90 " "; /* Color for the map areas */ pattern1 c=cxb00026 v=s; /* Draw the map */ proc gmap map=my_map data=my_map anno=top_anno; id state county; choro state / coutline=white anno=anno_back nolegend discrete ; run; quit;