%let name=zoom; filename odsout '.'; /* Written by Robert Allison (Robert.Allison@sas.com) */ /* This is the zoomed-in version of the minard.gif located in this same directory... */ /* Reproduction of Minard's famous map of Napoleon's march on Moscow, similar to the following... Links: http://www.math.yorku.ca/SCS/Gallery/re-minard.html (links to many versions) http://www.spss.com/research/wilkinson/TheGrammarOfGraphics/minard.txt <-- data http://www.spss.com/research/wilkinson/TheGrammarOfGraphics/GOG.html http://www.math.yorku.ca/SCS/Gallery/minard/ http://www.math.yorku.ca/SCS/Gallery/minard/Minard-IML.jpg (lat/long!) http://www.math.yorku.ca/SCS/Gallery/minard/NapoleonsMarch.iml (iml code w/ lat/long) http://www.math.yorku.ca/SCS/Gallery/minard/march-animated.gif http://www.math.yorku.ca/SCS/Gallery/minard/minard-nvizn.gif http://www.math.yorku.ca/SCS/Gallery/minard/minard-odt.jpg http://www.edwardtufte.com/tufte/posters http://www.edwardtufte.com/tufte/minard http://www.csiss.org/classics/content/58 */ goptions reset=global; options fmtsearch=(sashelp.mapfmts); data my_map; set maps.europe maps.asia; /* we want the unprojected values, since we're projecting them ourselves */ /* Also convert longitude to a proper 'eastlong' value, so it matches the annotate long/lat values */ x=long*-1; y=lat; country=id; /* This just makes the code easier to follow */ length countryname $20; countryname=put(country,glcnsm.); run; proc sql; create table countries as select unique country, countryname from my_map; quit; run; data countries; set countries; run; /* Got long/lat data from... http://www.spss.com/research/wilkinson/TheGrammarOfGraphics/minard.txt I added one extra obsn for group=2 during the river-crossing, so it was more evident that the 2 groups combined... 28.3 54.4 28000 R 2 */ data armyline; length direc $1; length direction $10; input lon lat surviv direc group ; n+1; if direc eq 'A' then direction='advance'; else if direc eq 'R' then direction='retreat'; /* Convert degrees to radians */ x=atan(1)/45 * lon; y=atan(1)/45 * lat; datalines; 24.0 54.9 340000 A 1 24.5 55.0 340000 A 1 25.5 54.5 340000 A 1 26.0 54.7 320000 A 1 27.0 54.8 300000 A 1 28.0 54.9 280000 A 1 28.5 55.0 240000 A 1 29.0 55.1 210000 A 1 30.0 55.2 180000 A 1 30.3 55.3 175000 A 1 32.0 54.8 145000 A 1 33.2 54.9 140000 A 1 34.4 55.5 127100 A 1 35.5 55.4 100000 A 1 36.0 55.5 100000 A 1 37.6 55.8 100000 A 1 37.5 55.7 98000 R 1 37.0 55.0 97000 R 1 36.8 55.0 96000 R 1 35.4 55.3 87000 R 1 34.3 55.2 55000 R 1 33.3 54.8 37000 R 1 32.0 54.6 24000 R 1 30.4 54.4 20000 R 1 29.2 54.4 20000 R 1 28.5 54.3 20000 R 1 28.3 54.4 20000 R 1 24.0 55.1 60000 A 2 24.5 55.2 60000 A 2 25.5 54.7 60000 A 2 26.6 55.7 40000 A 2 27.4 55.6 33000 A 2 28.7 55.5 30000 A 2 29.2 54.3 30000 R 2 28.5 54.2 30000 R 2 28.3 54.3 28000 R 2 28.3 54.4 28000 R 2 27.5 54.5 20000 R 2 26.8 54.3 12000 R 2 26.4 54.4 14000 R 2 24.6 54.5 8000 R 2 24.4 54.4 4000 R 2 24.2 54.4 4000 R 2 24.1 54.3 4000 R 2 24.0 55.2 22000 A 3 24.5 55.3 22000 A 3 24.6 55.8 6000 A 3 24.2 54.4 6000 R 3 24.1 54.3 6000 R 3 ; run; proc sort data=armyline out=armyline; by group n; run; %let maxwidth=5; /* Maximum width of line */ %let maxdot=2.5; /* Maximum size (radius) of vertex fill-in dot */ data armyline; length function style color $ 12 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3'; set armyline; by group; anno_flag=1; when='a'; if first.group then do; function='move'; output; end; else do; if direc eq 'A' then color='cxaddd8e'; else if direc eq 'R' then color='cxfe0000'; function='draw'; size=(surviv/340000) * &maxwidth; output; /* Vertex fill-in dot -- this gives smoother transition between line segments, like a kneecap/elbow, and also gives a place for html charttips (since line segments don't support charttips) */ size=(surviv/340000) * &maxdot; function='pie'; style='psolid'; rotate=360; length html $ 1000; html= 'title='|| quote( trim(left(put(surviv,comma7.0))||' troops during '||trim(left(direction)) )||' '); output; /* Output nother vertex dot, behind the map, to guarantee that you have a big enough charttip area to mouseover, even when the line gets very skinny. */ when='B'; size=3; output; end; run; data citydots; infile datalines pad truncover; input lon lat city $ 11-31 trivia $ 33-100; /* Convert degrees to radians */ x=atan(1)/45 * lon; y=atan(1)/45 * lat; /* Leaving these 2 cities out, to make map less cluttered */ /* 30.4 53.9 Mohilow : 27.6 53.9 Minsk : */ datalines; 24.0 55.0 Kowno : June 22-24, crossed Nieman river into (then) Russia 25.3 54.7 Wilna : Entered Wilna June 28, departed July 16 27.7 55.2 Gloubokoe : 28.7 55.5 Polotzk : Advance Aug 16/17 30.2 55.3 Witebsk : Advance July 28 32.0 54.8 Smolensk : Advance Aug 17/18, Retreat Nov 9-13 33.2 54.9 Dorogobouge : During retreat, winter/snow hit Nov 6th 34.4 55.5 Chjat : 36.0 55.5 Mojaisk : Sept 7 37.6 55.8 Moscou : Taken Sep 14, departed Oct 19 36.6 55.3 Tarantino : 36.5 55.0 Malo-jarosewli : 34.3 55.2 Wixma : Entered Nov 2 (during retreat) 30.4 54.5 Orscha : Nov 19 29.2 54.4 Bobr : Nov 25 28.5 54.3 Studienska : Nov 26-28 - Crossed Berezina River on handmade bridges 26.8 54.3 Molodexno : 26.4 54.4 Smorgoni : Left Dec 5 ; run; /* Annotate a black dot/pie at each city, with mouseover html charttip showing the city name. (This variable *must* be called 'html'.) */ data citydots; length html $ 1000; length function style color $ 12 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3' when 'a'; set citydots; anno_flag=2; function='pie'; color='black'; style='psolid'; position='5'; rotate=360; size=.3; html=''; output; function='label'; text=trim(left(city)); position='3'; size=2.0; style="'verdana'"; html= 'title='|| quote( trim(left(city))||' '||trim(left(trivia))||' ') ||' '|| 'href="http://www.google.com/search?hl=en&q='||trim(left(city))||'+Napoleon+1812"' ; output; run; /* gridspac is the number of latitude degrees (height) my temperature chart is allowed to take. Since my gridspac=1 and my temperatures range from 0 to -40 degrees centigrade (not to be confused with degrees latitude, or degrees farenheit) then each 1 degree of latitude is equal to 30 degrees celcius temperature */ %let gridspac=1.0; /* Got this temperature data from ... http://www.math.yorku.ca/SCS/Gallery/minard/minard-odt.jpg Note that they are a little colder than the numbers in ... http://www.math.yorku.ca/SCS/Gallery/minard/minard.txt My data is a combination - the temperature from the first link, and the longitude from the 2nd link (above). */ data colddots; infile datalines pad truncover; input lon lat_to tempdegc days date $ 21-27; tempdegf = (9/5)*tempdegc + 32; lat=52.6 + &gridspac - (((0-tempdegc)/40)*&gridspac); datalines; 37.6 55.8 0 6 Oct 18 36.0 55.1 0 6 Oct 24 33.2 54.8 -13 16 Nov 9 32.0 54.6 -26 5 Nov 14 29.2 54.3 -14 10 28.5 54.2 -25 4 Nov 28 27.2 54.4 -30 3 Dec 1 26.7 54.3 -38 5 Dec 6 25.3 54.4 -33 1 Dec 7 ; run; data colddots; length html $ 1000; length function style color $ 12 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3' when 'a'; set colddots; /* Light blue ('ice cold') dots for each temperature reading) */ function='pie'; color='cxC6E2FF'; style='psolid'; position='5'; rotate=360; size=.8; html= 'title='|| quote( trim(left(tempdegc))||' degrees centigrade (aka, '||trim(left(tempdegf))||' farenheit) '||trim(left(date))||' '); output; /* gray border around the blue dot/marker */ color='gray'; style='pempty'; output; /* Now draw dotted line up to the city where this temp was recorded */ function='draw'; line=2; size=.1; color='cxC6E2FF'; lat=lat_to; output; /* Annotate dates along bottom of plot */ function='label'; color='gray'; size=2.5; style='"arial"'; position='e'; text=trim(left(date)); lat=52.6; output; run; /* These are the gridlines for the annotated temperature plot */ data coldline; length function style color $ 12 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3' when 'a'; color='gray'; size=.1; position='5'; style="'arial'"; /* new (-40 C) bottom line */ function='move'; lon=37.6; lat=52.6 + 0*(&gridspac/4); output; function='label'; color='black'; position='c'; size=2.5; text='. -40° F'; output; function='draw'; color='gray'; size=.1; lon=25.3; output; function='label'; color='black'; position='a'; size=2.5; text='-40° C.'; output; /* bottom line (-30 C)*/ function='move'; lon=37.6; lat=52.6 + 1*(&gridspac/4); output; function='label'; color='black'; position='c'; size=2.5; text='. -22° F'; output; function='draw'; color='gray'; size=.1; lon=25.3; output; function='label'; color='black'; position='a'; size=2.5; text='-30° C.'; output; /* 2nd from bottom line (-20 C) */ function='move'; lon=37.6; lat=52.6 + 2*(&gridspac/4); output; function='label'; color='black'; position='c'; size=2.5; text='. -4° F'; output; function='draw'; color='gray'; size=.1; lon=25.3; output; function='label'; color='black'; position='a'; size=2.5; text='-20° C.'; output; /* 3rd from bottom line (-10 C) */ function='move'; lon=37.6; lat=52.6 + 3*(&gridspac/4); output; function='label'; color='black'; position='c'; size=2.5; text='. 14° F'; output; function='draw'; color='gray'; size=.1; lon=25.3; output; function='label'; color='black'; position='a'; size=2.5; text='-10° C.'; output; /* top line (0 C)*/ function='move'; lon=37.6; lat=52.6 + 4*(&gridspac/4); output; function='label'; color='black'; position='c'; size=2.5; text='. 32° F'; output; function='draw'; color='gray'; size=.1; lon=25.3; output; function='label'; color='black'; position='a'; size=2.5; text='0° C.'; output; /* right side */ function='move'; lon=37.6; lat=52.6; output; function='draw'; color='gray'; size=.1; lat=52.6 + 4*(&gridspac/4); output; /* left side */ function='move'; lon=25.3; lat=52.6; output; function='draw'; color='gray'; size=.1; lat=52.6 + 4*(&gridspac/4); output; run; data colddots; set coldline colddots; anno_flag=5; /* Convert degrees to radians */ x=atan(1)/45 * lon; y=atan(1)/45 * lat; run; /* Annotate country names at desired long/lat locations */ data names; input lon lat countryname $ 11-31; /* Convert degrees to radians */ x=atan(1)/45 * lon; y=atan(1)/45 * lat; datalines; 25.3 55.9 Lithuania 26.7 56.1 Latvia 28.3 55.9 Belarus 32.5 55.9 Russia ; run; data names; length function style color $ 12 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3' when 'a'; set names; function='label'; text=trim(left(countryname)); color='gray'; style='"arial"'; position='5'; size=3; anno_flag=4; run; /* Combine the map and all the long/lat-based annotate datasets */ data combined; set my_map armyline citydots colddots names ; run; /* project the map and annotate datasets */ proc gproject data=combined out=combined dupok eastlong project=robinson latmax=56.5 latmin=52.0 longmin=23.4 longmax=39 ; id country; run; /* Separate the annotate datasets from the map */ data my_map armyline citydots names colddots; set combined; if anno_flag=1 then output armyline; else if anno_flag=2 then output citydots; else if anno_flag=4 then output names; else if anno_flag=5 then output colddots; else output my_map; run; /* Annotate a custom legend */ data legendanno; length function text color $12; xsys='3'; ysys='3'; hsys='3'; when='A'; function='LABEL'; size=2.75; style='"arial"'; position='6'; color='black'; text='Advance'; x=69.1; y=51.5; output; text='Retreat'; x=69.1; y=47.5; output; size=2; text='U'; x=68; y=51; style='marker'; color='cxaddd8e'; output; style='markere'; color='gray'; output; text='U'; x=68; y=47; style='marker'; color='cxfe0000'; output; style='markere'; color='gray'; output; run; /* Annotate a 'Powered by sas' logo */ data img; length function text $ 8; length color $ 8; xsys='3'; ysys='3'; hsys='3'; when='A'; html='title="Tell me more about SAS/Graph..." href="http://www.sas.com/technologies/bi/query_reporting/graph/factsheet.pdf" '; function='move'; x=1.1; y=9.8; output; function='image'; x=x+10; y=y+7; imgpath='powered_stat.gif'; style='fit'; output; run; /* Combine the annotate datasets in the desired order/layer */ data anno1; set armyline citydots colddots names legendanno img; run; GOPTIONS DEVICE=gif; goptions xpixels=1200 ypixels=540; ODS LISTING CLOSE; ODS HTML path=odsout body="&name..htm" (title="Napoleon's March (SAS Version of Minard's Map)") style=minimal gtitle nogfootnote ; goptions noborder; goptions gunit=pct htitle=5 htext=3 ftitle="arial/bo" ftext="arial"; pattern1 v=s c=tan r=500; title justify=left "Napoleon's Russian Campaign, 1812"; title2 justify=left "Plotted on modern map"; footnote justify=left h=10pt f="arial" "Mouse over markers and path for more info in chart tips"; footnote2 justify=left h=10pt f="arial" "Click on city names to launch google search"; proc gmap map=my_map data=countries anno=anno1; id country; choro countryname / levels=1 coutline=gray nolegend des="" name="&name"; run; quit; ODS HTML CLOSE; ODS LISTING;