%let name=friday13; filename odsout '.'; /* hsys='d' for sizing annotated text, is a new v9.2 feature */ data my_data; length dayname $20; format day date7.; format thirteenth comma5.1; do day = '01jan2009'd to '31dec2021'd by 1; dayofyear=0; dayofyear=put(day,julday3.); year=0; year=put(day,year4.); monname=trim(left(put(day,monname.))); downame=trim(left(put(day,downame.))); day_of_month=put(day,day.); if (downame eq 'Friday') and (day_of_month eq 13) then do; thirteenth=1; output; end; end; run; proc sql noprint; /* Macro variable containing minimum year */ select min(year) into :min_year from my_data; select max(year) into :max_year from my_data; /* Start your annotate label data for each year */ create table my_anno as select unique year from my_data; quit; run; /* My algorithm assumes you have an obs for each day, so create such a grid */ /* (not essential if you have no days with missing data, but go ahead and do it to be on the safe side) */ data grid_days; format day date7.; do day="01jan.&min_year"d to "31dec.&max_year"d by 1; weekday=put(day,weekday.); downame=trim(left(put(day,downame.))); monname=trim(left(put(day,monname.))); year=put(day,year.); output; end; run; /* Join your data with the grid-of-days */ proc sql noprint; create table my_data as select * from grid_days left join my_data on grid_days.day eq my_data.day; quit; run; /* Add some flyover chart tip (could also add href drilldown here */ data my_data; set my_data; length my_html $300; my_html='title='||quote( put(day,downame.)||'0D'x||put(day,date11.)); run; /* Create a 'map' of the days, suitable for use in gmap */ /* If you had 'real' data, it might not be sorted, so sort it here */ /* (must be sorted to use the 'by' below) */ proc sort data=my_data out=datemap; by year day; run; /* You must use 'by year', in order to use first.year */ /* You're starting with minimum date at top/left, max at bottom/right */ data datemap; set datemap; keep day x y; by year; if first.year then x_corner=1; else if trim(left(downame)) eq 'Sunday' then x_corner+1; /* If this factor is 7, there will be no space between years */ y_corner=((&min_year-year)*8)-weekday; x=x_corner; y=y_corner; output; x=x+1; output; y=y-1; output; x=x-1; output; run; /* Create darker outline to annotate around each month, since gmap can't automatically do a 2-level outline. */ data outline; set datemap; length yr_mon $15; yr_mon=trim(left(put(day,year.)))||'_'||trim(left(put(day,month.))); order+1; run; /* Sort it, so you can use 'by' in next step */ proc sort data=outline out=outline; by yr_mon order; run; proc gremove data=outline out=outline; by yr_mon; id day; run; data outline; length color function $8; xsys='2'; ysys='2'; size=1.75; when='a'; color=''; set outline; by yr_mon; if first.yr_mon then function='poly'; else function='polycont'; run; data my_anno; set my_anno; length text $20 color $10 style $35; function='label'; position='4'; xsys='2'; ysys='2'; hsys='d'; when='a'; x=-6; y=((&min_year-year)*8)-1.25; style='albany amt/bold'; size=9; text=trim(left(year)); output; style=''; x=-.1; size=8; text='Sunday'; output; y=y-1; text='Monday'; output; y=y-1; text='Tuesday'; output; y=y-1; text='Wednesday'; output; y=y-1; text='Thursday'; output; color='red'; y=y-1; text='Friday'; output; color=''; y=y-1; text='Saturday'; output; run; data month_anno; length text $20; function='label'; position='5'; xsys='2'; ysys='2'; hsys='d'; when='a'; size=8; y=1; spacing=4.4; x=3.5; text='JAN'; output; x=x+spacing; text='FEB'; output; x=x+spacing; text='MAR'; output; x=x+spacing; text='APR'; output; x=x+spacing; text='MAY'; output; x=x+spacing; text='JUN'; output; x=x+spacing; text='JUL'; output; x=x+spacing; text='AUG'; output; x=x+spacing; text='SEP'; output; x=x+spacing; text='OCT'; output; x=x+spacing; text='NOV'; output; x=x+spacing; text='DEC'; output; run; data my_anno; set my_anno month_anno; run; /* Put a fake map rectangle/area to the top/left of the map, to give room for the annotated labels on the left & top (otherwise, gmap will rescale itself to fill all available space, and leave no white-space to the left for the labels). Note that this 'fake' map area will be drawn in the 'coutline' color you specify in the gmap options (you could maybe get clever here, and use 2 pattern colors, and have this one be the same color as the background, if you really want it to be totally 'invisible' (but then you have to figure out how to get it to not show up in the legend :) */ data fake; day=1; color='white'; x=-10; y=1; output; x=x-.001; y=y+.001; output; x=x+.002; output; run; data datemap; set datemap fake; run; goptions device=png; goptions xpixels=800 ypixels=1300; goptions noborder; ODS LISTING CLOSE; ODS HTML path=odsout body="&name..htm" (title="When is Friday the 13th?") style=sasweb; goptions htitle=16pt htext=8pt ftitle="albany amt/bold" ftext="albany amt"; goptions ctext=gray33; pattern1 v=s c=cxff0000; title1 ls=1.5 c=cxff0000 move=(+17,+0) "13 Years of Friday The 13th!"; proc gmap data=my_data map=datemap all anno=my_anno; label thirteenth = 'Friday The 13th'; id day; choro Thirteenth / discrete nolegend coutline=graycc cempty=graycc anno=outline html=my_html des='' name="&name"; run; quit; ODS HTML CLOSE; ODS LISTING;