%let name=airline; filename odsout '.'; /* I started with data from examples on the following website... http://tomcat.corda.com/airline/layout.html And then tried to make a dashboard that was more functional than theirs :) */ goptions xpixels=1000 ypixels=775; /* Reverse of the previous gradient */ data grad_rev; length function color $10 style $12; retain xsys '3' ysys '3' when 'b' style 'solid'; orig_red= input('84',hex2.); orig_green=input('c7',hex2.); orig_blue= input('d6',hex2.); do i=2 to 100 by 2; count+1; function='move'; x=0; y=i; output; function='bar'; x=100; y=i-2; percent=(count-1)/(60); 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; /* Solid annotated background */ data solid; length function color $10 style $12; retain xsys '3' ysys '3' when 'b' style 'solid'; function='move'; x=0; y=0; output; function='bar'; x=100; y=100; color="cx6baed6"; output; run; data fare; format num_seats comma6.0; format revenue_pct percent5.0; input category $ 1-20 num_seats revenue_pct; length myhtml $ 200; myhtml='title='||quote( trim(left(translate(category,' ','/')))||': seats='||trim(left(put(num_seats,comma6.0)))||' revenue='||trim(left(put(revenue_pct,percent5.0))) ) ||' '|| 'href="http://www.sas.com"'; datalines; Full Fare 1350 .15 7 Day/Adv 980 .25 21 Day/Adv 3450 .50 FF/Redeem 721 .00 FF/Upgrade 1804 .10 Empty 476 .00 ; run; data tickets; label kind='00'x; input kind $ 1-30 category $ 32-52 count; length myhtml $ 200; myhtml='title='||quote( trim(left(kind))||', '||trim(left(category))||': '||trim(left(count)) ) ||' '|| 'href="http://www.sas.com"'; datalines; Internet Sales Full Fare 220 Internet Sales 7 Day Adv 420 Internet Sales 21 Day Adv 1534 Internet Sales FF Redeem 350 Internet Sales FF Upgrade 820 Travel Agent Sales Full Fare 340 Travel Agent Sales 7 Day Adv 240 Travel Agent Sales 21 Day Adv 1050 Travel Agent Sales FF Redeem 150 Travel Agent Sales FF Upgrade 540 Direct Sales Full Fare 590 Direct Sales 7 Day Adv 320 Direct Sales 21 Day Adv 866 Direct Sales FF Redeem 221 Direct Sales FF Upgrade 444 Direct Sales Empty 0 ; run; /* Added the one 'Empty' obsn so that color patterns would be assigned consistently in the pie & bar. */ data loading; format value percent6.0; input flight $1-15 value; length myhtml $ 200; myhtml='title='||quote( trim(left(flight))||': '||trim(left(put(value,percent5.0))) ) ||' '|| 'href="http://tomcat.corda.com/airline/flightDetail.html?flight='||trim(left(flight))||'"'; datalines; 1765-LAX>SEA .73 1769-LAX>SLC .43 1711-LAX>DEN .75 1745-LAX>SFO .96 1787-LAX>DFW .55 1730-LAX>PHX .48 ; run; data states; set maps.states (where= ( fipstate(state) in ('WA' 'OR' 'CA' 'NV' 'ID' 'MT' 'WY' 'UT' 'CO' 'AZ' 'NM' 'TX' 'OK' 'KS' 'NE' 'SD' 'ND') and (density < 3) ) ); flag=1; run; proc sql; create table statedata as select unique state, 1 as value from states; quit; run; data cityanno; input airport $ 1-3 zipcode cans; citydesc=zipcity(zipcode); cards; IAH 77032 17 DFW 75201 4 KCI 66101 8 DEN 80202 14 SLC 84101 5 PHX 85034 1 LAS 89101 3 SAN 92101 4 LAX 90001 23 SFO 94102 9 PWM 97201 4 SEA 98101 19 ; run; proc sql; create table cityanno as select cityanno.*, zipcode.city, zipcode.zip, -1*zipcode.x as longitude, zipcode.y as latitude from cityanno left join sashelp.zipcode on cityanno.zipcode=zipcode.zip; quit; run; /* Convert the degrees to radians */ data cityanno; set cityanno; x=atan(1)/45 * longitude; y=atan(1)/45 * latitude; run; /* Create a red dot, with link & chart-tip for each regional office */ data cityanno; length function style color $ 8 position $ 1 text $ 20 html $1024; retain xsys ysys '2' hsys '3' when 'a'; set cityanno; flag=2; /* This is the flyover text & drilldown */ html='title='||quote( trim(left(airport))||' ('||trim(left(citydesc))||') - '||trim(left(cans))||' cancellations' )|| ' href='||quote( 'http://www.sas.com' )||' '; function='pie'; rotate=360; position='5'; style='psolid'; size=1.75; if cans ge 17 then do; color='cyan'; size=2.5; end; else if cans ge 8 then color='red'; else if cans ge 5 then color='orange'; else color='cx00ff00'; output; /* make a black ring around pie, to help distinguish overlapping ones */ color='gray'; style=pempty; line=1; output; run; data combined; set states cityanno; run; proc gproject data=combined out=combined project=robinson dupok; id state; run; data states cityanno; set combined; if flag eq 1 then output states; else if flag eq 2 then output cityanno; run; /* Now, add a "fake legend" using annotated stuff ... */ data legendanno; length function text $8 style $12; xsys='3'; ysys='3'; hsys='3'; when='A'; function='PIE'; line=0; angle=0.0; rotate=360.0; /* add dots ... */ size=1.75; style='SOLID'; color='cx00ff00'; x=85; y=60; output; style='mempty'; color='gray'; output; style='SOLID'; color='orange'; x=85; y=55; output; style='mempty'; color='gray'; output; style='SOLID'; color='red'; x=85; y=50; output; style='mempty'; color='gray'; output; size=2.5; style='SOLID'; color='cyan'; x=85; y=45; output; style='mempty'; color='gray'; output; /* add text ... */ function='LABEL'; size=2.75; style='"arial"'; position='6'; color='dag'; text='1-4 '; x=88; y=60; output; text='5 '; x=88; y=55; output; text='8-14'; x=88; y=50; output; text='17-23'; x=88; y=45; output; run; data cityanno; set cityanno legendanno; run; /* Customize the ods style, so you can color the background of the webpage... */ ods path work.template(update) sashelp.tmplmst; proc template; define style styles.airline; parent = styles.default; replace color_list / "paleturq" = cx96CDCD ; replace colors / "docbg" = color_list("paleturq") ; end; GOPTIONS DEVICE=png; /* In v9.2, dev=png supports >256 colors, which helps the 3d cylinder bar chart sides look smoother. */ ODS LISTING CLOSE; ODS HTML path=odsout body="&name..htm" (title="Airline Dashboard") style=airline; goptions noborder; goptions gunit=pct htitle=5.5 ftitle="arial/bold" htext=3.2 ftext="arial"; goptions nodisplay; goptions cback=cx63659c; title1 box=1 bcolor=grayee color=blue justify=left "Average Flight Loading - February" move=(1pct,-8pct) "Flights Originating at LAX"; title2 h=8pct " "; axis1 label=('Average Load %') order=(0 to 1 by .2) minor=none offset=(0,0); axis2 label=none value=(justify=right) offset=(3.7,6); pattern1 v=s c=cxA67D3D; pattern2 v=s c=cx8968CD; pattern3 v=s c=cx8B0A50; pattern4 v=s c=cx483D8B; pattern5 v=s c=cx668B8B; pattern6 v=s c=cxA2C257; proc gchart data=loading anno=grad_rev; hbar3d flight / nostats ascending type=sum sumvar=value subgroup=flight raxis=axis1 maxis=axis2 autoref cref=gray coutline=gray noframe nolegend /* width=7 space=0 */ html=myhtml name='load' ; run; title1 box=1 bcolor=grayee color=blue justify=left "Fare Distribution - February"; axis1 label=(angle=90 'Number of Seats') minor=none offset=(0,0); axis2 label=none split='/'; pattern1 v=s c=cx98F5FF; pattern2 v=s c=cx9BC4E2; pattern3 v=s c=cxEAADEA; pattern4 v=s c=gray; pattern5 v=s c=cxEAEAAE; pattern6 v=s c=cx1C86EE; proc gchart data=fare anno=grad_rev; vbar3d category / descending type=sum sumvar=num_seats subgroup=category shape=cylinder /* coutline is a new v9.2 option - if you don't have v9.2 then delete this line */ coutline=gray raxis=axis1 maxis=axis2 autoref cref=gray noframe nolegend /* width=20 */ html=myhtml name='vbar' ; run; pattern1 v=psolid c=cx98F5FF; pattern2 v=psolid c=cx9BC4E2; pattern3 v=psolid c=cxEAADEA; pattern4 v=psolid c=gray; pattern5 v=psolid c=cxEAEAAE; pattern6 v=psolid c=cx1C86EE; proc gchart data=fare; title; footnote justify=center " " move=(28pct,18pct) height=8pct "Fare Revenue"; /* sizing the text in the footnote is a little tricky, because this graph is resized differently than the other graphs (ie, it's made smaller) when it is greplay'd into the final template. (just use trial-and-error on the height=?pct) */ pie3d category / type=sum sumvar=revenue_pct explode='21 Day/Adv' '7 Day/Adv' 'FF/Upgrade' 'Full Fare' noheading value=none slice=none coutline=gray nolegend html=myhtml name='pieinset' ; run; /* tickets: kind, category, count */ title1 box=1 bcolor=grayee color=blue justify=left "Ticket Purchase Distribution - February"; footnote; legend1 label=none shape=bar(2.5,3) mode=share across=1 position=(bottom right) offset=(-13,11); proc gchart data=tickets anno=grad_rev; pie3d category / type=sum sumvar=count group=kind across=2 down=2 noheading value=none slice=none coutline=gray legend=legend1 html=myhtml name='pie' ; run; title1 box=1 bcolor=grayee color=blue justify=left "Cancelled Flights - February"; title2 h=3pct " "; title3 a=90 h=5pct " "; title4 a=-90 h=30pct " "; footnote h=1pct " "; pattern1 v=s c=tan; proc gmap data=statedata map=states anno=grad_rev; id state; choro value / levels=1 coutline=gray nolegend anno=cityanno name='map' ; run; goptions display; proc greplay tc=tempcat nofs igout=gseg; tdef airline des='Airline' 1/llx = 2 lly = 51 ulx = 2 uly = 97 urx =49 ury = 97 lrx =49 lry = 51 color=black 2/llx = 2 lly = 3 ulx = 2 uly = 49 urx =49 ury = 49 lrx =49 lry = 3 color=black 3/llx =51 lly = 51 ulx =51 uly = 97 urx =98 ury = 97 lrx =98 lry = 51 color=black 4/llx =51 lly = 3 ulx =51 uly = 49 urx =98 ury = 49 lrx =98 lry = 3 color=black 5/llx =30 lly = 25.5 ulx =30 uly = 43 urx =46 ury = 43 lrx =46 lry = 25.5 ; template=airline; treplay 1:load 3:pie 2:vbar 4:map 5:pieinset des="" name="&name" ; run; quit; quit; ODS HTML CLOSE; ODS LISTING;