ANIMATED GIF OUTPUT (Click on image or magnifying glass to view larger image at full speed!)
How Santa solves his massive traveling salesman problem remains a mystery (SAS Viya on quantum computers?). So, in the spirit of Santa's DasherBoard (SAS VA-based, from @TravisMurphy), Optimal Santa Route (SAS VA-based, from, @FalkoSchulz), and Santa's Information Dashboard (SAS/GRAPH-based, from @ RobertAllison_S AS), here's my decidedly less-ambitious SAS ODS Graphics-based 'Traveling Santa Tour' through the capital cities of the contiguous 48 states using an optimized 'flight plan' lifted from a PROC OPTNET example. Happy Holidays, all!
CODE
* Fun With SAS ODS Graphics: Animated "Traveling Santa Tour" Thru U.S. Capital Cities;
data FlightPlan; * Create SAS dataset of cities in Santas "flight plan";
infile datalines dlm=',' dsd; * Cities are output of PROC OPTNET example in SAS documentation;
input capital : $30.@@; * See "Example 2.7 Traveling Salesman Tour through US Capital Cities";
if capital^=''; * support.sas.com/documentation/cdl/en/ornoaug/66084/HTML/default/viewer.htm#ornoaug_optnet_examples07.htm;
seq+1; * Add sequence number for tour path;
datalines;
Montgomery, Montgomery, Tallahassee, Columbia, Raleigh, Richmond, Washington, Annapolis, Dover, Trenton, Hartford,
Providence, Boston, Concord, Augusta, Montpelier, Albany, Harrisburg, Charleston, Columbus, Lansing,
Madison, Saint Paul, Bismarck, Pierre, Cheyenne, Denver, Salt Lake City, Helena, Boise City, Olympia, Salem,
Sacramento, Carson, Phoenix, Santa Fe, Oklahoma City, Austin, Baton Rouge, Jackson, Little Rock, Jefferson City,
Topeka, Lincoln, Des Moines, Springfield, Indianapolis, Frankfort, Nashville, Atlanta, Montgomery
;
proc sql; * Get Geo info for cities from SAS USCITY map dataset (SASFILES points to SAS Map datasets);
create table capitalsGeo(keep=seq capitalCity stateCode x y lat long rename=(x=xC y=yC)) as
select f.seq,
upcase(case when f.capital='Carson' then 'Carson City' else f.capital end) as capitalCity, /* Fix: add 'City' to 'Carson' */
u.StateCode, u.x, u.y, u.lat, u.long
from flightplan f join sasfiles.uscity u on u.capital='Y' and f.capital=u.city order by seq;
* Create dataset with info needed to chart Santas route;
data CapitalsGeoLines(keep=seq xC yC x1 y1 x2 y2 CapitalCity: StateCode: miles);
set CapitalsGeo end=eof; * Get data for "FROM" city from prior observation;
x1=lag(xC); y1=lag(yC); lat1=lag(lat); long1=lag(long); CapitalCity1=lag(CapitalCity); StateCode1=lag(StateCode);
x2=xC; y2=yC; * X2/Y2=points for "TO" City;
if ^eof then CapitalCityLbl=CapitalCity;* Do not repeat starting city name at end of tour;
miles=geodist(lat1, long1, lat, long, 'M');
if _n_>1; * No line for first point;
seq=seq-1; * Re-adjust seq # to account for skipped point;
data usa(keep=state segment x y pid); * Get outlines of 48 contiguous U.S. states from SAS US dataset;
set sasfiles.us(where=(statecode not in('AK','HI', 'PR'))); * Remove AK HI PR;
length pid $ 8; * State outlines are one or more polygons;
pid=put(state, 3.0)||put(segment, 3.0); * Generate unique polygon identifier;
* For more info, see 'Micro Maps' at blogs.sas.com/content/graphicallyspeaking/2015/04/19/micro-maps/;
data capitalGeoUSA; * Merge city points and state outline data;
set usa capitalsGeoLines;
proc template; * Template for Santa Tour Map (chart is composite of multiple plots);
define statgraph Maps;
mvar FromCity ToCity Distance TotDistance; * Macro variables for inset box;
begingraph / subpixel=on border=false;
* Microsoft Windows 10 Santa emoji image (no art budget!);
symbolimage name=santa image='/folders/myfolders/Windows10Santa.png';
layout overlayEquated / walldisplay=none border=false
xaxisopts=(offsetmin=0.0 offsetmax=0.0 display=none)
yaxisopts=(offsetmin=0.0 offsetmax=0.0 display=none);
* 1. Polygon plot of 48 contiguous U.S. states;
polygonPlot x=x y=y id=pid / display=(fill outline) fillattrs=(color=CXc4cace) outlineattrs=(color=white); * "Metallic silver" fill;
* 2. Vector plot of lines between cities visited;
vectorplot x=x2 y=y2 xorigin=x1 yorigin=y1 / lineattrs=(color=black);
* 3. Scatter plot of cities visited - circles;
scatterplot x=xC y=yC / markerattrs=(symbol=circle color=black) datalabel=capitalCityLbl datalabelattrs=(color=black);
* 4. Scatter plot of last city visited - Win10 Santa emoji marker;
scatterplot x=xS y=yS / markerattrs=(symbol=santa color=black size=30);
* 5. Chart title;
entry halign=center " A Traveling Santa Tour of U.S. State Capitals " / valign=top textattrs=(size=15 color=CX296e01 weight=bold); * "Metallic green";
* 6. Inset box of from/to cities and distances in lower left corner;
layout gridded / rows=4 order=columnmajor valign=bottom halign=left border=true;
entry halign=left "From: " / textattrs=(size=11 color=CXa62c2b weight=bold); * "Metallic red";
entry halign=left "To: " / textattrs=(size=11 color=CXa62c2b weight=bold);
entry halign=left "Distance: " / textattrs=(size=11 color=CXa62c2b weight=bold);
entry halign=left "Total: " / textattrs=(size=11 color=CXa62c2b weight=bold);
entry halign=left FromCity / textattrs=(size=11 color=CXa62c2b weight=bold);
entry halign=left ToCity / textattrs=(size=11 color=CXa62c2b weight=bold);
entry halign=left Distance / textattrs=(size=11 color=CXa62c2b weight=bold);
entry halign=left TotDistance / textattrs=(size=11 color=CXa62c2b weight=bold);
endlayout;
* 6. Larger Win10 Santa emoji image in lower right corner;
drawimage "/folders/myfolders/Windows10Santa.png" / anchor=bottomright x=100 y=0 drawspace=wallpercent height=18 heightunit=percent scale=fitheight;
endlayout;
endgraph;
end;
run;
ods _all_ close; * Create animated GIF for 'Santa Tour';
options papersize=('11 in', '6.6 in') printerpath=gif animation=start
nodate nonumber animduration=2 animloop=NO NOANIMOVERLAY;
ods printer file='/folders/myfolders/Santa.gif';
ods graphics / height=6.8in width=11in imagefmt=gif antialias labelmax=2000; * Specify labelmax to minimize city name "collisons";
%macro drawlines;
proc sql noprint; * Get number of frames to draw (# of from/to pairs);
select count(*) into :numlines from CapitalsGeoLines;
quit;
%do l=1 %to &numlines; * Draw "frames" containing image with cities visited thus far;
%if &l=1 | &l=&numlines %then options animduration=2; %else options animduration=.75;; * Make first/last frames longer;
data capitalGeoUSAwork; * Dataset for frame 1 has 1 city, 2 has 2 cities, ...;
set capitalGeoUSA(where=(seq<=&l));
totmiles+miles; * Tally cumulative miles;
if seq=&l then do; * Create points, macro variables for last line drawn;
xS=xC; yS=yC; * X/Y coordinates for Santa Claus location;
call symput('FromCity',trim(CapitalCity1)||', '||StateCode1); * From city/state;
call symput('ToCity',trim(CapitalCity)||', '||StateCode); * To city/state;
call symput('Distance',compress(put(miles,comma6.),' ')||' mi'); * Miles (current leg);
call symput('TotDistance',compress(put(totmiles,comma6.),' ')||' mi'); * Cumulative miles;
end; * Render frame;
proc sgrender data=capitalGeoUSAwork template=Maps;
run;
%end;
%mend;
%drawlines; * Draw the frames;
options printerpath=gif animation=stop; * All done - close animated GIF file;
ods printer close;
MICROSOFT WINDOWS 10 SANTA EMOJI (Windows10Santa.png)
... View more