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
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!
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.