%let name=zodiac; filename odsout '.'; /* hsys='d' for sizing annotated text, is a new v9.2 feature */ /* Using dates from: http://en.wikipedia.org/wiki/Chinese_zodiac */ data my_data; format startdate enddate date9.; informat startdate enddate date9.; input startdate enddate animal $ 21-50; datalines; 05feb1924 23jan1925 Rat 24jan1925 12feb1926 Ox 13feb1926 01feb1927 Tiger 02feb1927 22jan1928 Rabbit 23jan1928 09feb1929 Dragon 10feb1929 29jan1930 Snake 30jan1930 16feb1931 Horse 17feb1931 05feb1932 Goat 06feb1932 25jan1933 Monkey 26jan1933 13feb1934 Rooster 14feb1934 03feb1935 Dog 04feb1935 23jan1936 Pig 24jan1936 10feb1937 Rat 11feb1937 30jan1938 Ox 31jan1938 18feb1939 Tiger 19feb1939 07feb1940 Rabbit 08feb1940 26jan1941 Dragon 27jan1941 14feb1942 Snake 15feb1942 04feb1943 Horse 05feb1943 24jan1944 Goat 25jan1944 12feb1945 Monkey 13feb1945 01feb1946 Rooster 02feb1946 21jan1947 Dog 22jan1947 09feb1948 Pig 10feb1948 28jan1949 Rat 29jan1949 16feb1950 Ox 17feb1950 05feb1951 Tiger 06feb1951 26jan1952 Rabbit 27jan1952 13feb1953 Dragon 14feb1953 02feb1954 Snake 03feb1954 23jan1955 Horse 24jan1955 11feb1956 Goat 12feb1956 30jan1957 Monkey 31jan1957 17feb1958 Rooster 18feb1958 07feb1959 Dog 08feb1959 27jan1960 Pig 28jan1960 14feb1961 Rat 15feb1961 04feb1962 Ox 05feb1962 24jan1963 Tiger 25jan1963 12feb1964 Rabbit 13feb1964 01feb1965 Dragon 02feb1965 20jan1966 Snake 21jan1966 08feb1967 Horse 09feb1967 29jan1968 Goat 30jan1968 16feb1969 Monkey 17feb1969 05feb1970 Rooster 06feb1970 26jan1971 Dog 27jan1971 14feb1972 Pig 15feb1972 02feb1973 Rat 03feb1973 22jan1974 Ox 23jan1974 10feb1975 Tiger 11feb1975 30jan1976 Rabbit 31jan1976 17feb1977 Dragon 18feb1977 06feb1978 Snake 07feb1978 27jan1979 Horse 28jan1979 15feb1980 Goat 16feb1980 04feb1981 Monkey 05feb1981 24jan1982 Rooster 25jan1982 12feb1983 Dog 13feb1983 01feb1984 Pig 02feb1984 19feb1985 Rat 20feb1985 08feb1986 Ox 09feb1986 28jan1987 Tiger 29jan1987 16feb1988 Rabbit 17feb1988 05feb1989 Dragon 06feb1989 26jan1990 Snake 27jan1990 14feb1991 Horse 15feb1991 03feb1992 Goat 04feb1992 22jan1993 Monkey 23jan1993 09feb1994 Rooster 10feb1994 30jan1995 Dog 31jan1995 18feb1996 Pig 19feb1996 06feb1997 Rat 07feb1997 27jan1998 Ox 28jan1998 15feb1999 Tiger 16feb1999 04feb2000 Rabbit 05feb2000 23jan2001 Dragon 24jan2001 11feb2002 Snake 12feb2002 31jan2003 Horse 01feb2003 21jan2004 Goat 22jan2004 08feb2005 Monkey 09feb2005 28jan2006 Rooster 29jan2006 17feb2007 Dog 18feb2007 06feb2008 Pig 07feb2008 25jan2009 Rat 26jan2009 13feb2010 Ox 14feb2010 02feb2011 Tiger 03feb2011 22jan2012 Rabbit 23jan2012 09feb2013 Dragon 10feb2013 30jan2014 Snake 31jan2014 18feb2015 Horse 19feb2015 07feb2016 Goat 08feb2016 27jan2017 Monkey 28jan2017 18feb2018 Rooster 19feb2018 04feb2019 Dog 05feb2019 24jan2020 Pig ; run; /* Output an obsn for every day between start and end date */ data my_data; set my_data; do day=startdate to enddate by 1; output; end; run; data my_data; set my_data; length htmlvar $200; format day date9.; dayofyear=0; dayofyear=put(day,julday3.); year=0; year=put(day,year4.); monname=trim(left(put(day,monname.))); output; 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 date9.; 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 myhtmlvar $200; myhtmlvar= 'title='||quote( trim(left(put(day,downame.)))||'0D'x|| put(day,date11.)||'0D'x|| 'Animal: '||trim(left(animal))); 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='black'; set outline; by yr_mon; if first.yr_mon then function='poly'; else function='polycont'; run; data my_anno; set my_anno; length text $10; xsys='2'; ysys='2'; hsys='d'; when='a'; function='label'; position='4'; x=-6; y=((&min_year-year)*8)-1.25; style='albany amt/bold'; size=8; 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; y=y-1; text='Friday'; output; y=y-1; text='Saturday'; output; run; data month_anno; length text $10; 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 border; /* Use xpixels/ypixels, rather than hsize/vsize, to preserve proportions */ goptions xpixels=800 ypixels=8000; ODS LISTING CLOSE; ODS HTML path=odsout body="&name..htm" (title="Chinese Zodiac - calendar chart") style=htmlblue nogtitle; goptions htitle=14pt htext=8pt ftitle="albany amt/bold" ftext="albany amt"; goptions ctext=gray33; legend1 shape=bar(.15in,.15in) offset=(-5,-1) position=(top right) across=1 label=none order=('Rat' 'Ox' 'Tiger' 'Rabbit' 'Dragon' 'Snake' 'Horse' 'Goat' 'Monkey' 'Rooster' 'Dog' 'Pig'); title1 c=black height=20pt "Chinese Zodiac"; title2 c=black height=16pt "(mouse over days to see details)"; proc gmap data=my_data map=datemap all anno=my_anno; id day; choro animal / coutline=graycc cempty=white legend=legend1 anno=outline html=myhtmlvar des='' name="&name"; run; quit; ODS HTML CLOSE; ODS LISTING;