CODE
* Fun w/SAS ODS Graphics: Animated Easter Bunny from points in math-aids.com graphing worksheet;
data EasterBunny(keep=shapeID x y polyX polyY);
length shapeID $ 20. x 8 y 8;
retain shapeID;
infile "/folders/myfolders/EB.txt" lrecl=1000; * File contains points to plot from math-aids.com;
input;
if _infile_=:'Shape' then
shapeID=_infile_;
else
do;
i=1;
do while(scan(_infile_, i, ' ')^='');
x=scan(_infile_, i, '(), ');
y=scan(_infile_, i+1, '(), ');
i+2; * List of shapes to color on final slide;
if shapeID in ('Shape 16' 'Shape 17' 'Shape 21' 'Shape 22'
'Shape 3' 'Shape 4' 'Shape 5' 'Shape 6'
'Shape 9' 'Shape 10' 'Shape 11' 'Shape 12') then
do; polyX=x; polyY=y; end;
output;
end;
end;
* Lines to draw;
data line(keep=function drawspace x1 y1 x2 y2 polyX polyY);
set EasterBunny;
by shapeID notsorted;
function='line';
drawspace='datavalue';
x1=lag(x);
y1=lag(y);
x2=x;
y2=y;
if ^first.shapeID;
%macro gentemplate(final=Y); * Draw the bunny!;
proc template;
define statgraph ebtemplate;
begingraph;
dynamic linenum;
layout overlayequated / walldisplay=none
xaxisopts=(display=none offsetmin=0.001 offsetmax=0.001)
yaxisopts=(display=none offsetmin=0.001 offsetmax=0.001);
scatterplot x=x y=y / markerattrs=(size=0);
%if &final=Y %then %do;
polygonplot x=PolyX y=PolyY id=ShapeID / display=(fill) label=none fillattrs=(color=pink);
ellipseparm semimajor=.5 semiminor=.5 xorigin=-2 yorigin=2.5 slope=0 / display=all outlineattrs=(thickness=2pt) fillattrs=(color=pink);
ellipseparm semimajor=.5 semiminor=.5 xorigin=-.5 yorigin=2.5 slope=0 / display=all outlineattrs=(thickness=2pt) fillattrs=(color=pink);
%end;
entry halign=right linenum / valign=bottom textattrs=(size=32 weight=bold color=lightgray);
annotate;
endlayout;
endgraph;
end;
run;
%mend;
* Create animated GIF with one incremental plot for each line;
ods _all_ close;
options papersize=('4 in', '4 in') printerpath=gif animation=start
nodate nonumber animduration=1.8 animloop=YES NOANIMOVERLAY;
ods printer file='/folders/myfolders/EB.gif';
ods graphics / width=4in height=4in imagefmt=GIF;
%macro drawlines;
proc sql noprint;
select count(*) into :numlines from line;
quit;
%gentemplate(final=Y); * Display final image before drawing lines;
options animduration=1.8;
proc sgrender data=easterbunny template=ebtemplate sganno=line;
dynamic linenum=" ";
options animduration=.04; * Line-by-line drawing;
%gentemplate(final=N);
%do l=1 %to &numlines; * Draw incremental image with lines thus far;
proc sgrender data=easterbunny template=ebtemplate sganno=line(obs=&l);
dynamic linenum="&l";
%end;
run;
%mend;
%drawlines;
options printerpath=gif animation=stop;
ods printer close;
DATA
Shape 1 -10 -7 -10 -8.5 -9.5 -9.5 -9 -10 -8 -10.5 -10 -7 Shape 2 -10 -7 -10.5 -6 -10 -4 -8.5 -3 -7 -2.5 -6.5 -2.5 -5.5 -3 -4.5 -4.5 -3.5 -7 -2.5 -10 -2 -12 -2.5 -14 -4 -14.5 -5.5 -14 -7 -12 -8 -10.5 Shape 3 -9.5 -5.5 -8.5 -5.5 -8 -6 -8 -7 -9 -7 -9.5 -6 -9.5 -5.5 Shape 4 -8.5 -3.5 -7.5 -4 -7 -5 -7.5 -6 -8 -5.5 -8.5 -4.5 -8.5 -3.5 Shape 5 -6.5 -3.5 -6 -3.5 -5.5 -4 -5.5 -5 -6 -5.5 -6.5 -5 -7 -4 -6.5 -3.5 Shape 6 -7 -6.5 -6 -6.5 -5.5 -7 -4.5 -8.5 -4 -10 -3.5 -12 -3.5 -13.5 -5 -13 -6 -11.5 -7.5 -9 -8 -8 -7.5 -7 -7 -6.5 Shape 7 -6.5 -2.5 -5 -2 -4.5 -2.5 -5.5 -3 Shape 8 -1 -3.5 -2.5 -5 -1.5 -5 -2 -6 -1 -6 -1.5 -7 -1 -6.5 -1 -8.5 Shape 9 6.5 -7.5 7.5 -6 8.5 -6 8.5 -6.5 8 -7.5 7.5 -7.5 6.5 -7.5 Shape 10 7.5 -8.5 8 -8 9 -7.5 10 -7.5 9.5 -8.5 8.5 -9 8 -9 7.5 -8.5 Shape 11 8 -10 8.5 -9.5 9.5 -9.5 10 -10 9.5 -10.5 8.5 -10.5 8 -10 Shape 12 0.5 -12.5 1.5 -11 3 -10 5 -8.5 6.5 -8.5 7.5 -9 7.5 -10 7 -11 5.5 -11.5 3.5 -12.5 2 -13 0.5 -12.5 Shape 13 -2 -12 -1 -12 -0.5 -11 2 -9 4 -7.5 5.5 -6.5 7 -5.5 9 -5.5 10.5 -6.5 11 -8 10.5 -10 9 -11.5 5 -13 2 -14 0 -14 -1 -12 Shape 14 -4.5 -2.5 -4 -2.5 -2 -3 0 -3 2 -2.5 3 -2.5 4 -3.5 5 -5 5.5 -6 5.5 -6.5 Shape 15 5.5 -6 6 -5.5 8 -4.5 10 -3.5 11 -2 11.5 -1 11 0.5 10 1.5 9 1.5 7.5 1 6.5 0.5 6 1 5.5 0.5 5.5 -1 5 -2 4 -2.5 3 -2.5 Shape 16 0 7 0.5 9 2.5 12 5 13 6 12 5.5 11 4 9.5 2 8.5 0 7 Shape 17 -3 6 -3.5 7 -5 9.5 -6.5 11 -8 11.5 -9 11.5 -9.5 11 -9.5 10 -8.5 9 -6.5 7.5 -4 6.5 -3 6 Shape 18 -5 -2 -6 -1.5 -6.5 -1 -7 -0.5 -7 1 -6 2 -5.5 3 -5 4 -4.5 5 -3.5 5.5 -5 6.5 -7 7.5 -9.5 8.5 -11.5 9.5 -12 11 -11.5 12 -10 12.5 -9 12.5 -7 12 -5 10.5 -4 9 -2.5 7 -2 6.5 -3 6 -3.5 5.5 Shape 19 -2.5 7 -1.5 6.5 -2 7.5 -1 6.5 0 6 1.5 7.5 4 9 6 10 7.5 11 8 12.5 7.5 13.5 6 14 4 13.5 2 12 0.5 10 -0.5 8 -1 6.5 Shape 20 0 6 1.5 5 2.5 4 3 3 4 2 4.5 1 5 0 4.5 -1 4 -1.5 3 -2.5 Shape 21 -3.5 1 -3 0.5 -4 0 -5 0.5 -3.5 1 -2.5 1.5 -1.5 1.5 -1 1 -0.5 1.5 -0.5 1 -1 0.5 -1.5 0.5 -2 1 -1.5 1.5 Shape 22 -0.5 1.5 0 1.5 1.5 1.5 2.5 1.5 3 1 2 0.5 1 1 1.5 1.5 Shape 23 -1 0.5 -1 -1 0.5 -1 1 -0.5 Shape 24 -1 -1 -2 -1.5 -3 -1
Love it
Technically this is completely off topic as it doesn't use ODS or SAS/GRAPH, but I spent almost an hour doing it so I can't not post it now. Here's the same in JavaScript, using a slightly modified version of the original SAS code to create the SVG, and vivus.js to animate it:
The SAS code to generate the SVG looks like:
data _null_;
length shapeID $ 20. x 8 y 8;
gh = 40; * grid height ;
gw = 30; * grid width ;
file "/pub/ht/bunny/my.svg"; * target SVG location and write header ;
if _n_ eq 1 then
put '<svg width="' gw +(-1) '0" height="' gh +(-1) '0" viewBox="0 0 ' gh gw '" id="bunny">';
infile "/pub/programs/EB.txt" lrecl=1000 end=EOF; * File contains points to plot from math-aids.com;
input;
if _infile_=:'Shape' then do;
shapeID=_infile_;
if _n_ gt 1 then put '"/>'; * unless its the first element we will need to close last one ;
if shapeID in ('Shape 16' 'Shape 17' 'Shape 21' 'Shape 22'
'Shape 3' 'Shape 4' 'Shape 5' 'Shape 6'
'Shape 9' 'Shape 10' 'Shape 11' 'Shape 12') then
put '<polygon fill="none" stroke="#FF8A65" stroke-width="0.2" points="' @;
* if this is a polygon then it is a polygon, else it is a polyline ;
else put '<polyline fill="none" stroke="#FF8A65" stroke-width="0.2" points="' @;
end;
else do;
i=1;
do while(scan(_infile_, i, ' ')^='');
x=scan(_infile_, i, '(), ') + ((gw / 2));
put x +(-1) ',' @;
y=((gh / 2) - scan(_infile_, i+1, '(), '));
put y @;
i+2;
output;
end;
end;
if EOF then put '"/></svg>';
run;
The Vivus JS constructor to animate it looks like:
new Vivus('bunny', { type: 'oneByOne', duration: 800, pathTimingFunction: Vivus.EASE_OUT, });
If anyone wants to see the generated SVG or have a play around with the JavaScript code, the example is up on JsFiddle: http://jsfiddle.net/m3xswhnz/.
Hurray for long weekends.
Nik
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!
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.
Ready to level-up your skills? Choose your own adventure.