Repetition and iteration
Let’s say you want to call some block of code three times. Let’s say that block of code happens to rotate a rectangle. OK, well, here’s some code that runs if you click it:
rect.rotate(60);
Do it three times! (This is the real JavaScript, by the way, using the Raphael JavaScript vector graphics library.)
That’s the equivalent of
rect.rotate(60); rect.rotate(60); rect.rotate(60);
except that we get to see a nice spinning effect. Now, this repetition of code — as it is, just hard-coded to repeat three times — could be captured in an iteration:
var i, n = 3; for (i = 0; i < n; i++ ) { rect.rotate(60) }
This code effectively says: “Set i
to 0 and n
to 3. If i
is less
than n
, rotate the rectangle,
and increment i
. Repeat the ‘if’.”
If we wanted to rotate four times, we'd just need to change n
.
Slightly more complex iteration
Now we want a rotation that applies itself to a copy of the previously rotated rectangle. The magical function that does this (implementation to be described below) can be clicked here:
doSomethingMagical();
Again, let’s say three times is our goal. How would we implement this?
Hard-coded, it might look like this:
var rect1 = paper.rect(0, 0, 10, 10); // Make a rect var rect2 = paper.rect(0, 0, 10, 10).rotate(60); // Make a rect and rotate 60 var rect3 = paper.rect(0, 0, 10, 10).rotate(120); // Make a rect and rotate 120
Pretty ugly, and not very reusable. (What if we’d rather it be 30 degrees, not 40?) An iterative procedure would look like:
var i, n = 3, lastRect = paper.rect(0, 0, 10, 10), currentRect; for (i = 0; i < n; i ++) { currentRect = lastRect.clone().rotate(60); lastRect = currentRect; }
To understand this, you need to know that clone
makes a new
object that has the attributes of its “master”, including rotation.
In English this iteration is something like (excluding the initial state of variables):
“If i
is less than n
, make a clone of lastRect
and rotate it by 60 degrees. Assign the variable lastRect
to the new
clone, increment i
, and repeat.”
Note that the iteration works by updating the variable
lastRect
to point
to whatever the last rectangle created was, so that, when it loops for the
next time, it’s copying the new rectangle.
Higher-order functions
The idea of iteratively applying a transformation to a graphic — and by “iteratively” here I mean, as above, making copies as you go — isn’t really tied to whether that transformation is a rotation or a translation or whatever. Ideally, we could give a function (called “iterate” below) a base object, the number of times to apply an iteration, and the transformation function to run; e.g.
var rect = paper.rect(0, 0, 10, 10); var fn = function (obj) { obj.translate(20, 0); }; iterate(rect, 3, fn);
Which would give us:
JavaScript nicely lets us capture the transformation function as a variable
(“fn” above) and pass it as an argument to another function. The fact
that iterate
accepts a function as an argument means it is
a “higher-order” function: a function which either accepts a function
as input or produces one as output.
So, what would the implementation of iterate
look like?
var iterate = function(obj, n, fn) { if (!(n > 0)) { return; } newObj = obj.clone(); fn(newObj); iterate(newObj, n - 1, fn); };
It’s similar in many ways to the for
loops above. In English:
“Return if n
is not greater than 0. Make a clone of obj
,
and call fn
passing in the newly created clone as an argument.
Then iterate
with the new object, 1 less than n
,
and fn
.”
Iterate calls itself repeatedly until n
is 0, and each time
it does it creates a new object, transforming it.
Symmetry functions
It’s possible, then, to describe symmetries of these vector objects with some ease in JavaScript, though certain kinds of operations are much more simply described as a procedure than others. Translating, like above, is easy. “Pivoting” objects around a point is more difficult: it requires tracking that point across function calls and determining its relation to the current object with some trig functions.
With a bit more effort, it’s possible to wrap the symmetries produced in a object that can, itself, iterate. For instance, let’s say we have the following peculiar shape, produced by the following code:
var r = paper.partOfAnElizabethanStrap();
Remember that r
is the base Raphael object. Now we call iterate
,
but note this is a different iterate
than the one above:
var r1 = r.iterate(1, function() { this.scale(-1, 1); // Reflect over y-axis this.translate(100, 0); });
In this implementation of iterate
I’m doing something special. It accepts a
number of iterations to perform and a function as its
arguments. The function is called in the context of the cloned objects, and so I apply the
transformations to this
. The result of iterate
I put in a variable
r1
.
r1
is not the cloned object that’s been moved and reflected. It’s the
group of both of those objects; the result of the iteration. And the iteration can itself be
iterated, which lets us say:
r2 = r1.iterate(1, function() { this.rotateAround(180, 50, -45); this.translate(-50, 0); });
This pivots and translates the symmetry group r1
as a whole. The “pivoting” is done by a method called rotateAround
.
The result, which is all four objects, is stored in r2
. To finish:
r3 = r2.iterate(4, function() { this.translate(100, 0); });
You can see my implementation of iterate
at
Github.
(There’s also an implementation of rotateAround
).
Note that it’s very
proof-of-concept and experimental now. There’s currently some bugs when using multiple
calls to rotate
and rotateAround
over iterations.
If only I knew my trig.