It might snow this weekend here at the SAS headquarters! This would be the first snow of the season for us, and it got me thinking about snow again (see some of my previous blog posts about snow). Apparently these thoughts have manifested themselves in my computer graphics work ... in the form of a snow animation. Follow along, and I'll share with you how I created it!
But before we get started on the computer code, here's a picture of my yard decoration. It seems appropriate, since we usually get ice rather than snow:
Now, let's create some virtual snow! I got this idea from an old blog post by my friend Rick Wicklin. He created a snow animation as a fun way to demonstrate some sophisticated techniques: creating Koch snowflake polygons using SAS/IML, using uniform spacing with random perturbations to position the snowflakes, and using matrix operations to rotate and scale the snowflakes. His results were beautiful & mesmerizing - you should check out his blog post!
I couldn't hope to improve upon Rick's snowfall animation, therefore my goal was to create a simpler one. Here's what I came up with ...
Rather than mathematically creating a Koch snowflake polygon, I used a font character. To find such a font character, I ran the Windows Character Map, and perused the fonts until I found the following ...
Now that I know a snowflake is character 54 (hexadecimal) in the Wingdings font, I can use the following code to annotate it on a SAS graph:
function='label'; position='+'; size=25; color="Affffffdd";
Rather than calculating uniform spacing with random perturbations, I hard-coded the starting x/y positions for each snowflake on a 1-100 scale:
input x y;
And whereas Rick used matrix transformations to rotate all the points describing his snowflake polygons, I could use the 'angle' variable in my annotate dataset to rotate the font characters. I wanted a little extra action, therefore I used a different starting angle for each snowflake, and rotated some of my snowflakes clockwise, and some counter-clockwise.
/* each snowflake has a different starting angle */
/* make some of the snowflakes rotate clockwise, and some counterclockwise */
if ranuni(123)>.4 then angle_direction=1;
I then looped through the data, creating the number of frames I wanted in my animation (calculating a new x/y position, and rotation angle for each snowflake in each frame):
data snowflake_positions; set snowflake_positions;
do frame = 1 to iterations;
/* code to change the positions and angle of the snowflakes */
I generated a graph for each animation frame, suppressing the things that I would normally be shown in the graph (in this case, the gmap), and only showing the animated snowflakes. Note that the anno_message (containing the background sky, and text message) stays the same in each frame, and the anno_snowflakes (containing the snowflake positions and angles) changes with each frame. Annotating with a 'by' variable can get a little tricky, but it can be very useful!
proc gmap data=snowflake_positions map=my_map anno=anno_message;
choro frame / levels=1 nolegend coutline=same anno=anno_snowflakes;
And here is my finished snow animation - enjoy!
Feel free to download my code, and try it out (try modifying the text to give your animation a customized message!)
Update: My animation seems to have worked! - Here's a picture of the *real* snow we got!