BookmarkSubscribeRSS Feed
Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10



Seeing Chevell Parker pull-donut-charts-out-of-a-SAS-Excel-hat at SASGF 2017 made me try to recall, "When did I see a donut chart for the first time?" Well, SAS/GRAPH users have had donut charts in their bag-of-dataviz-tricks for about a quarter of a century. But that wasn't it. Even earlier users of donut charts, including me, were kids who grew up playing the sadly-discontinued Cadaco All-Star Baseball board game, where pie-charts-with-a-hole-in-the-middle were used to present MLB batting stats on game discs since 1941. So, after reading Prashant Hebbar and Sanjay Matange's SASGF paper on Easy Polar Graphs with SG Procedures, I thought to myself, "Could I use SAS ODS Graphics to create All-Star Baseball disc knock-offs?"



* Fun with SAS ODS Graphics: Knock-off of All-Star Baseball "donut chart" discs;

data mlbstats;                                             * Create disc categories from MLB stats;
infile cards dlm='|' dsd firstobs=2;                       * Read MLB stats;
input Player : $30. Pos : $20. Year Team : $3. LG : $2. G AB R H TB _2B _3B HR RBI BB IBB SO SB CS AVG OBP SLG OPS GO_AO;
PlayerNum+1;                                               * Assign player sequence #;
player=upcase(player);                                     * Player name;
pos=upcase(pos);                                           * Player position;
wedgeID=01; stat=HR; output;                               * Home run;
wedgeID=02; stat=(AB-H-SO)*GO_AO/(GO_AO+1)/3; output;      * Ground out I;
wedgeID=03; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out I;
wedgeID=04; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out II;
wedgeID=05; stat=_3B; output;                              * Triple;
wedgeID=06; stat=(AB-H-SO)*GO_AO/(GO_AO+1)/3; output;      * Ground out II;
wedgeID=07; stat=(H-HR-_3B-_2B)/2; output;                 * Single I;
wedgeID=08; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out III;
wedgeID=09; stat=BB/2; output;                             * Walk I;
wedgeID=09; stat=BB/2; output;                             * Walk II;
wedgeID=10; stat=SO/2; output;                             * Strikeout I;
wedgeID=10; stat=SO/2; output;                             * Strikeout II;
wedgeID=11; stat=_2B; output;                              * Double;
wedgeID=12; stat=(AB-H-SO)*GO_AO/(GO_AO+1)/3; output;      * Ground out III;
wedgeID=13; stat=(H-HR-_3B-_2B)/2; output;                 * Single II;
wedgeID=14; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out IV;
cards;                                                     * 2016-17 MLB Cubs batting stats (source:;
Kris*Bryant|Third Base|2016|CHC|NL|155|603|121|176|334|35|3|39|102|75|5|154|8|5|0.292|0.385|0.554|0.939|0.59|
Anthony*Rizzo|First Base|2016|CHC|NL|155|583|94|170|317|43|4|32|109|74|8|108|3|5|0.292|0.385|0.544|0.928|0.91|
Dexter*Fowler|Center Field|2016|CHC|NL|125|456|84|126|204|25|7|13|48|79|0|124|13|4|.276|.393|.447|.840|0.91
proc sql;                                                  * Assign % of total stats to each wedge;
create table mlbstatsrnd as 
select m.*, m.stat/s.sumstat as pctsumstat,           
      case when wedgeID^=1 then ranuni(100) end as rndseq  /* Keep HR wedge at top of disc, others random order */
from mlbstats m, (select PlayerNum, sum(stat) as sumstat from mlbstats group by 1) s 
where m.PlayerNum=s.PlayerNum order by PlayerNum, rndseq;

data mlbstatschart;                                        * Prep data for charting;
retain startradians;                                       * Wedge start (radians);
set mlbstatsrnd;
by PlayerNum;                                              * Center HR wedge at top;
if first.playernum then StartRadians=90/360*2*pi-.5*pctsumstat*2*pi;
xWedge=cos(StartRadians); yWedge=sin(startradians);        * Wedge start x/y coordinates;
xTxt=.84*cos(StartRadians+.5*pctsumstat*2*pi);             * Wedge category # x/y coordinates;
startradians=startradians+pctsumstat*2*pi;                 * Update wedge start (radians);
if first.playernum then do;                            
  xPlayer=0; yPlayer=.55;                                  * Player name x/y coordinates;
  xPos=0; yPos=-.45;                                       * Player position x/y coordinates;

options nobyline;                                          * Time to make a disc with GTL!;
ods graphics on / reset antialias width=5in height=5in noborder;
proc template;
 define statgraph AllStarBaseballDisc;
   layout overlayequated / equatetype=square walldisplay=none
     xaxisopts=(display=none offsetmin=0.001 offsetmax=0.001)
     yaxisopts=(display=none offsetmin=0.001 offsetmax=0.001);
     ellipseparm semimajor=1 semiminor=1 slope=0           /* Grey outer circle */
       xorigin=0 yorigin=0 / display=(fill) fillattrs=(color=lightgrey);
     vectorplot x=xWedge y=yWedge xorigin=0 yorigin=0 /    /* Stat wedge dividers */
       arrowheads=false lineattrs=(color=black thickness=2pt);
     ellipseparm semimajor=.68 semiminor=.68 slope=0      /* Red inner circle */
       xorigin=0 yorigin=0 / display=all fillattrs=(color=red) outlineattrs=(color=black thickness=5pt);
     textplot x=xTxt y=yTxt text=wedgeID /                /* Stat # */
       position=center textattrs=(color=black size=20pt weight=bold) strip=true;  
     textplot x=xPlayer y=yPlayer text=Player /           /* Player name */  
       position=bottom textattrs=(color=white size=16pt weight=bold) strip=true splitchar='*' splitpolicy=splitalways;
     textplot x=xPos y=yPos text=Pos /                    /* Player position */ 
       position=top textattrs=(color=white size=16pt weight=bold) strip=true;

proc sgrender data=mlbstatschart template=AllStarBaseballDisc;
by playernum;                                            * One disc per player;
Pyrite | Level 9

Nice!  You could also make it interactive with d3.js (one of my favorite js libraries):


Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10

A spinner would be fun! Smiley Happy



Registration is open! SAS is returning to Vegas for an AI and analytics experience like no other! Whether you're an executive, manager, end user or SAS partner, SAS Innovate is designed for everyone on your team. Register for just $495 by 12/31/2023.

If you are interested in speaking, there is still time to submit a session idea. More details are posted on the website. 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Get the $99 certification deal.jpg



Back in the Classroom!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 2 replies
  • 2 in conversation