Friday, June 13, 2014

Programming Geometric Art

A few weeks ago I was listening the the brilliant podcast 99% Invisible. The particular episode was all about quatrefoils, which are cloverleaf shaped geometric designs. In the episode they point out that quatrefoils feature prominently in high class objects, like gothic cathedrals and in luxury products (e.g. Louis Vuitton bags). The episode really stuck with me. It was so interesting. These simple shapes can convey so much meaning, much of it subconsciously (and now for you more consciously, because you too are going to start seeing quatrefoils everywhere, especially for my friends at Duke!)

I have always been fascinated with kaleidoscopes and geometric designs. I love Arabic art and gothic patterns. There something soothing about geometric designs, they have order and organization but at the same time an organic familiarity because similar processes arise in nature.  



This got me thinking. At a base level geometric art is just an algorithm. Draw a line here, move 10 degrees over, draw another line, repeat. I know a little about algorithms and I started to wonder if I could make anything pretty in R (R being an ironic choice because 99% of what I do in R is decidedly not pretty). At first I set out to make quatrefoils, but I never did figure it out (and if you do, paste your code in the comments section below).  Then I reset my sights on just making something interesting. 

Below are the first 5 pleasing things I've managed to produce along with the R code that draws them.  The first 4 were designs I made intentionally. It's a fun exercise. Find
or imagine something you want to draw, then think about the algorithm that would make it, and finally translate that into code. It's one of those tasks that uses a lot of your brain.

Of the images below, I'm most proud of the one called "angles", because I got the process from idea to algorithm to code on the first try! All the others took some trial and error, with interesting squiggles and blobs emerging along the way.       

These pictures raise a lot of questions which are beyond my grasp of geometry and trig.  For example, in the mystic rose below why do all those internal circles arise? What is their radius? Why are there voids? There must be some way to derive answers to these things mathematically, and I'm sure if I meditated on it long enough I could probably figure it out. Unfortunately, for now I'm a little too busy to remedy my own ignorance.

Also, "mystic rose" isn't my name.  I did some googling and that seems to be the standard name for that object.  I'm a geneticist.  I'd have called it the "full diallel". :) (And a hellacious one at that. 625 crosses. You'd need to be pretty ambitious or foolish.)   

Finally, if you decide to run the R code below, it is best in regular old vanilla R, because it draws one line at a time and you can see the pattern emerge (and if you have an old slow computer like mine it will emerge at a nice human pace). When I plotted these in RStudio the image wouldn't print to the screen until it was done, which kind of kills the effect (I recommend running some of the code, it's mesmerizing).   

##mystic rose
rad = function(x) (x*pi)/180
plot(c(0,0), c(0,0), ty='n', xlim=c(-1,1), ylim=c(-1, 1))

deg.rot = 15
for (i in seq(0,360, deg.rot)) {
         for (j in seq(0, 360, deg.rot)) {
               lines(c(cos(rad(i)), cos(rad(j))), c(sin(rad(i)), sin(rad(j))), lwd=0.5)
         }
}
mystic rose 
##circles
v = seq(0,360,45)
rad = function(x) (x*pi)/180
plot(0,0, ty='n', xlim=c(-20,20), ylim=c(-20,20))
for (i in v) {
    for (j in seq(1, 10, 1)){
        symbols(cos(rad(i))*j,sin(rad(i))*10, circles=1, add=T) 
    }
}

circles

#angles
rad = function(x) (x*pi)/180
v = seq(45,360,45)
steps = seq(0,1,.025)
plot(c(0,0), c(0,0), ty='n', xlim=c(-1,1), ylim=c(-1, 1))
for (i in v) {
         next_angle = i + 45
         for (j in steps) {
         lines(c(cos(rad(i))*j, cos(rad(next_angle))*(1-j)), c(sin(rad(i))*j, sin(rad(next_angle))*(1-j)), lwd=0.5)
        }
}

angles 
##betas
v = seq(2, 1502, 15)**.75
plot(0,0, xlim=c(0,1), ylim=c(-20,20), ty='n')
for (i in v) {
    lines(seq(0,1,0.01), dbeta(seq(0,1,.01), shape1=i, shape2=i), lwd=.1)
    lines(seq(0,1,0.01), -dbeta(seq(0,1,.01), shape1=i, shape2=i), lwd=.1)
}
betas also tells a story. it's drawn using a beta distribution. it shows the uncertainty around a proportion estimated to be at 50% frequency as a function of an ever increasing sample size. it moves really fast at first, but quickly hits diminishing returns.  at the end, adding hundreds of samples does very little to increase statistical power.
betas
##trig functions
v = seq(0,pi*.25, pi/10)
v2 = seq(0,pi*0.5, pi/200)
plot(c(0,0), c(0,0), ty='n', xlim=c(-120,120), ylim=c(-150, 150))
for(i in v) {
            for (cons1 in seq(0,100,10) ){
                        for (cons2 in seq(0,100,5)) {
    xf = function (i) cons1*cos(i)+cos(3*i)+cos(2*i)+cos(4*i)
    yf = function(i) cons2*sin(i)+cons1*sin(3*i)
    lines(xf(v2), yf(v2), lwd=.05)
    lines(-xf(v2), -yf(v2),  lwd=.05)
                }
    }
}
trig functions